From 8004ad0bb325b9da9b70dbab705b6604e65cedf8 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Wed, 31 Jan 2018 16:02:30 -0500 Subject: [PATCH 01/48] field path wip --- .../firebase/firestore/model/field_path.cc | 252 ++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 Firestore/core/src/firebase/firestore/model/field_path.cc diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc new file mode 100644 index 00000000000..0789894f940 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -0,0 +1,252 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_replace.h" + +namespace firebase { +namespace firestore { +namespace model { + +namespace { +bool IsValidIdentifier(const std::string& segment) { + if (segment.empty()) { + return false; + } + if (segment.front() != '_' && !std::isalpha(segment.front())) { + return false; + } + if (std::any_of(segment.begin(), segment.end(), [](const unsigned char c) { + return c != '_' && !std::isalnum(c); + })) { + return false; + } + + return true; +} +} // namespace + +class FieldPath { + using SegmentsT = std::vector; + + public: + using const_iterator = SegmentsT::const_iterator; + + FieldPath() = default; + + template + FieldPath(const IterT begin, const IterT end) : segments_{begin, end} { + } + + FieldPath(std::initializer_list list) + : segments_{list.begin(), list.end()} { + } + + const std::string& operator[](const size_t index) const { + return at(index); + } + + const std::string& at(const size_t index) const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), + "index %u out of range", index); + return segments_[i]; + } + + const std::string& front() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), + "Cannot call front on empty path"); + return at(0); + } + const std::string& back() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), + "Cannot call back on empty path"); + return at(size() - 1); + } + + size_t size() const { + return segments_.size(); + } + + bool empty() const { + return segments_.empty(); + } + + const_iterator begin() const { + return segments_.begin(); + } + const_iterator end() const { + return segments_.end(); + } + + FieldPath Append(const std::string& segment) const { + auto appended = segments_; + appended.push_back(segment); + return FieldPath{std::move(appended)}; + } + + FieldPath Append(const FieldPath& path) const { + auto appended = segments_; + appended.insert(appended.end(), path.begin(), path.end()); + return FieldPath{std::move(appended)}; + } + + FieldPath PopFront(const size_t count = 1) const { + // OBC ASSERT + return FieldPath{segments_.begin() + count, segments_.end()}; + } + + FieldPath PopBack() const { + // OBC ASSERT + return FieldPath{segments_.begin(), segments_.end() - 1}; + } + + bool IsPrefixOf(const FieldPath& rhs) const { + // OBC empty range + return size() < rhs.size() && + std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); + } + + // std::hash + // to_string + + ////////////////////////////////////////////////////////////////////////////// + + static FieldPath FromServerFormat(const std::string& path) { + // TODO(b/37244157): Once we move to v1beta1, we should make this more + // strict. Right now, it allows non-identifier path components, even if they + // aren't escaped. Technically, this will mangle paths with backticks in + // them used in v1alpha1, but that's fine. + + SegmentsT segments; + std::string segment; + segment.reserve(path.size()); + + const auto finish_segment = [&segments, &segment] { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !segment.empty(), + "Invalid field path (%s). Paths must not be empty, begin with " + "'.', end with '.', or contain '..'", + path.c_str()); + // Move operation will clear segment, but capacity will remain the same + // (not strictly speaking required by the standard, but true in practice). + segments.push_back(std::move(segment)); + }; + + // Inside backticks, dots are treated literally. + bool insideBackticks = false; + // Whether to treat '\' literally or as an escape character. + bool escapedCharacter = false; + + for (const char c : path) { + if (c == '\0') { + break; + } + if (escapedCharacter) { + escapedCharacter = false; + segment += c; + continue; + } + + switch (c) { + case '.': + if (!insideBackticks) { + finish_segment(); + } else { + segment += c; + } + break; + + case '`': + insideBackticks = !insideBackticks; + break; + + case '\\': + escapedCharacter = true; + break; + + default: + segment += c; + break; + } + } + + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !insideBackticks, "Unterminated ` in path %s", path.c_str()); + // TODO(b/37244157): Make this a user-facing exception once we + // finalize field escaping. + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !escapedCharacter, "Trailing escape characters not allowed in %s", + path.c_str()); + + return FieldPath{std::move(segments)}; + } + + // OBC: do we really need emptypath? shared keypath? + + std::string CanonicalString() const { + std::string result; + bool is_first_segment = true; + + for (const auto& segment : segments) { + if (!is_first_segment) { + is_first_segment = false; + } else { + result += '.'; + } + + // OBC dot + const auto escaped = + absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); + const bool is_valid_id = IsValidIdentifier(escaped); + if (!is_valid_id) { + result += '`'; + } + result += escaped; + if (!is_valid_id) { + result += '`'; + } + } + + return result; + } + + private: + FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { + } + SegmentsT segments_; +}; + +bool operator<(const FieldPath& lhs, const FieldPath& rhs) { + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), + rhs.end()); +} + +bool operator==(const FieldPath& lhs, const FieldPath& rhs) { + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), + rhs.end()); +} + +} // namespace model +} // namespace firestore +} // namespace firebase From 2080f96eff8edbe63e688ad6f5d2b88fb68f26ba Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Wed, 31 Jan 2018 17:50:23 -0500 Subject: [PATCH 02/48] First iteration: - not yet compiled; - need to include abseil; - no tests; - internal classes only; - inheritance for interface; - no hash/to_string (perhaps not needed); - no emptypath/keypath (empty path perhaps not needed). --- .../firebase/firestore/model/field_path.cc | 135 ++++++++++++------ 1 file changed, 90 insertions(+), 45 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 0789894f940..55aea95701c 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -23,13 +23,16 @@ #include #include +#include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" +#include "absl/strings/str_split.h" namespace firebase { namespace firestore { namespace model { namespace { + bool IsValidIdentifier(const std::string& segment) { if (segment.empty()) { return false; @@ -45,24 +48,27 @@ bool IsValidIdentifier(const std::string& segment) { return true; } + +std::string EscapedSegment(const std::string& segment) { + // OBC dot + auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); + const bool needs_escaping = !IsValidIdentifier(escaped); + if (needs_escaping) { + escaped.push_front('`'); + escaped.push_back('`'); + } + return escaped; +} + } // namespace -class FieldPath { +class BasePath { + protected: using SegmentsT = std::vector; public: using const_iterator = SegmentsT::const_iterator; - FieldPath() = default; - - template - FieldPath(const IterT begin, const IterT end) : segments_{begin, end} { - } - - FieldPath(std::initializer_list list) - : segments_{list.begin(), list.end()} { - } - const std::string& operator[](const size_t index) const { return at(index); } @@ -112,12 +118,15 @@ class FieldPath { } FieldPath PopFront(const size_t count = 1) const { - // OBC ASSERT + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + count <= size(), "Cannot call PopFront(%u) on path of length %u", count, + size()); return FieldPath{segments_.begin() + count, segments_.end()}; } FieldPath PopBack() const { - // OBC ASSERT + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !empty(), "Cannot call PopBack() on empty path); return FieldPath{segments_.begin(), segments_.end() - 1}; } @@ -130,9 +139,32 @@ class FieldPath { // std::hash // to_string - ////////////////////////////////////////////////////////////////////////////// + protected: + BasePath() = default; + template + BasePath(const IterT begin, const IterT end) : segments_{begin, end} { + } + BasePath(std::initializer_list list) + : segments_{list.begin(), list.end()} { + } + FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { + } + ~BasePath() = default; + + private: + SegmentsT segments_; +}; + +class FieldPath : public BasePath { + public: + FieldPath() = default; + template + FieldPath(const IterT begin, const IterT end) : BasePath{begin, end} { + } + FieldPath(std::initializer_list list) : BasePath{list} { + } - static FieldPath FromServerFormat(const std::string& path) { + static FieldPath ParseServerFormat(const std::string& path) { // TODO(b/37244157): Once we move to v1beta1, we should make this more // strict. Right now, it allows non-identifier path components, even if they // aren't escaped. Technically, this will mangle paths with backticks in @@ -202,39 +234,15 @@ class FieldPath { return FieldPath{std::move(segments)}; } - // OBC: do we really need emptypath? shared keypath? - std::string CanonicalString() const { - std::string result; - bool is_first_segment = true; - - for (const auto& segment : segments) { - if (!is_first_segment) { - is_first_segment = false; - } else { - result += '.'; - } - - // OBC dot - const auto escaped = - absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); - const bool is_valid_id = IsValidIdentifier(escaped); - if (!is_valid_id) { - result += '`'; - } - result += escaped; - if (!is_valid_id) { - result += '`'; - } - } - - return result; + return absl::StrJoin(begin(), end(), '.', + [](std::string* out, const std::string& segment) { + out->append(EscapedSegment(segment)); + }); } - private: - FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { - } - SegmentsT segments_; + // OBC: do we really need emptypath? + // OBC: do we really need *shared* keypath? }; bool operator<(const FieldPath& lhs, const FieldPath& rhs) { @@ -247,6 +255,43 @@ bool operator==(const FieldPath& lhs, const FieldPath& rhs) { rhs.end()); } +class ResourcePath : public BasePath { + public: + ResourcePath() = default; + template + ResourcePath(const IterT begin, const IterT end) : BasePath{begin, end} { + } + ResourcePath(std::initializer_list list) : BasePath{list} { + } + + static ResourcePath Parse(const std::string& path) { + // NOTE: The client is ignorant of any path segments containing escape + // sequences (e.g. __id123__) and just passes them through raw (they exist + // for legacy reasons and should not be used frequently). + + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + path.find("//") == std::string::npos, + "Invalid path (%s). Paths must not contain // in them.", path.c_str()); + + // SkipEmpty because we may still have an empty segment at the beginning or + // end if they had a leading or trailing slash (which we allow). + auto segments = absl::StrSplit(path, '/', absl::SkipEmpty()); + return ResourcePath{std::move(segments)}; + } + + std::string CanonicalString() const { + // NOTE: The client is ignorant of any path segments containing escape + // sequences (e.g. __id123__) and just passes them through raw (they exist + // for legacy reasons and should not be used frequently). + + return absl::StrJoin(begin(), end(), '/'); + } + + private: + ResourcePath(SegmentsT&& segments) : BasePath{segments} { + } +}; + } // namespace model } // namespace firestore } // namespace firebase From 05cf106e139548e4440f63ae8e1a08dbc1b19aa5 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Wed, 31 Jan 2018 18:45:42 -0500 Subject: [PATCH 03/48] very wip splitting into files --- .../src/firebase/firestore/model/base_path.cc | 124 ++++++++++++++++++ .../src/firebase/firestore/model/base_path.h | 122 +++++++++++++++++ .../firebase/firestore/model/field_path.cc | 95 +------------- .../src/firebase/firestore/model/field_path.h | 28 ++++ 4 files changed, 276 insertions(+), 93 deletions(-) create mode 100644 Firestore/core/src/firebase/firestore/model/base_path.cc create mode 100644 Firestore/core/src/firebase/firestore/model/base_path.h create mode 100644 Firestore/core/src/firebase/firestore/model/field_path.h diff --git a/Firestore/core/src/firebase/firestore/model/base_path.cc b/Firestore/core/src/firebase/firestore/model/base_path.cc new file mode 100644 index 00000000000..b23c390e15d --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/base_path.cc @@ -0,0 +1,124 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/base_path.h" + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/str_split.h" + +template +class BasePath { + protected: + using SegmentsT = std::vector; + + public: + using const_iterator = SegmentsT::const_iterator; + + const std::string& operator[](const size_t index) const { + return at(index); + } + + const std::string& at(const size_t index) const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), + "index %u out of range", index); + return segments_[i]; + } + + const std::string& front() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), + "Cannot call front on empty path"); + return at(0); + } + const std::string& back() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), + "Cannot call back on empty path"); + return at(size() - 1); + } + + size_t size() const { + return segments_.size(); + } + + bool empty() const { + return segments_.empty(); + } + + const_iterator begin() const { + return segments_.begin(); + } + const_iterator end() const { + return segments_.end(); + } + + FieldPath Append(const std::string& segment) const { + auto appended = segments_; + appended.push_back(segment); + return FieldPath{std::move(appended)}; + } + + FieldPath Append(const FieldPath& path) const { + auto appended = segments_; + appended.insert(appended.end(), path.begin(), path.end()); + return FieldPath{std::move(appended)}; + } + + FieldPath PopFront(const size_t count = 1) const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + count <= size(), "Cannot call PopFront(%u) on path of length %u", count, + size()); + return FieldPath{segments_.begin() + count, segments_.end()}; + } + + FieldPath PopBack() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !empty(), "Cannot call PopBack() on empty path); + return FieldPath{segments_.begin(), segments_.end() - 1}; + } + + bool IsPrefixOf(const FieldPath& rhs) const { + // OBC empty range + return size() < rhs.size() && + std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); + } + + // std::hash + // to_string + + protected: + BasePath() = default; + template + BasePath(const IterT begin, const IterT end) : segments_{begin, end} { + } + BasePath(std::initializer_list list) + : segments_{list.begin(), list.end()} { + } + FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { + } + ~BasePath() = default; + + private: + SegmentsT segments_; +}; diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h new file mode 100644 index 00000000000..c9da43359a5 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -0,0 +1,122 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ + +namespace firebase { +namespace firestore { +namespace model { + +template +class BasePath { + protected: + using SegmentsT = std::vector; + + public: + using const_iterator = SegmentsT::const_iterator; + + const std::string& operator[](const size_t index) const { + return at(index); + } + + const std::string& at(const size_t index) const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), + "index %u out of range", index); + return segments_[i]; + } + + const std::string& front() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), + "Cannot call front on empty path"); + return at(0); + } + const std::string& back() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), + "Cannot call back on empty path"); + return at(size() - 1); + } + + size_t size() const { + return segments_.size(); + } + + bool empty() const { + return segments_.empty(); + } + + const_iterator begin() const { + return segments_.begin(); + } + const_iterator end() const { + return segments_.end(); + } + + T Append(const std::string& segment) const { + auto appended = segments_; + appended.push_back(segment); + return FieldPath{std::move(appended)}; + } + + FieldPath Append(const FieldPath& path) const { + auto appended = segments_; + appended.insert(appended.end(), path.begin(), path.end()); + return FieldPath{std::move(appended)}; + } + + FieldPath PopFront(const size_t count = 1) const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + count <= size(), "Cannot call PopFront(%u) on path of length %u", count, + size()); + return FieldPath{segments_.begin() + count, segments_.end()}; + } + + FieldPath PopBack() const { + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !empty(), "Cannot call PopBack() on empty path); + return FieldPath{segments_.begin(), segments_.end() - 1}; + } + + bool IsPrefixOf(const FieldPath& rhs) const { + // OBC empty range + return size() < rhs.size() && + std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); + } + + // std::hash + // to_string + + protected: + BasePath() = default; + template + BasePath(const IterT begin, const IterT end) : segments_{begin, end} { + } + BasePath(std::initializer_list list) + : segments_{list.begin(), list.end()} { + } + FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { + } + ~BasePath() = default; + + private: + SegmentsT segments_; +}; + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 55aea95701c..b4eec5fec23 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -14,6 +14,8 @@ * limitations under the License. */ +#include "Firestore/core/src/firebase/firestore/model/field_path.h" + #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include @@ -62,99 +64,6 @@ std::string EscapedSegment(const std::string& segment) { } // namespace -class BasePath { - protected: - using SegmentsT = std::vector; - - public: - using const_iterator = SegmentsT::const_iterator; - - const std::string& operator[](const size_t index) const { - return at(index); - } - - const std::string& at(const size_t index) const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), - "index %u out of range", index); - return segments_[i]; - } - - const std::string& front() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), - "Cannot call front on empty path"); - return at(0); - } - const std::string& back() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), - "Cannot call back on empty path"); - return at(size() - 1); - } - - size_t size() const { - return segments_.size(); - } - - bool empty() const { - return segments_.empty(); - } - - const_iterator begin() const { - return segments_.begin(); - } - const_iterator end() const { - return segments_.end(); - } - - FieldPath Append(const std::string& segment) const { - auto appended = segments_; - appended.push_back(segment); - return FieldPath{std::move(appended)}; - } - - FieldPath Append(const FieldPath& path) const { - auto appended = segments_; - appended.insert(appended.end(), path.begin(), path.end()); - return FieldPath{std::move(appended)}; - } - - FieldPath PopFront(const size_t count = 1) const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - count <= size(), "Cannot call PopFront(%u) on path of length %u", count, - size()); - return FieldPath{segments_.begin() + count, segments_.end()}; - } - - FieldPath PopBack() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !empty(), "Cannot call PopBack() on empty path); - return FieldPath{segments_.begin(), segments_.end() - 1}; - } - - bool IsPrefixOf(const FieldPath& rhs) const { - // OBC empty range - return size() < rhs.size() && - std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); - } - - // std::hash - // to_string - - protected: - BasePath() = default; - template - BasePath(const IterT begin, const IterT end) : segments_{begin, end} { - } - BasePath(std::initializer_list list) - : segments_{list.begin(), list.end()} { - } - FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { - } - ~BasePath() = default; - - private: - SegmentsT segments_; -}; - class FieldPath : public BasePath { public: FieldPath() = default; diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h new file mode 100644 index 00000000000..1798dff36b3 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -0,0 +1,28 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ + +namespace firebase { +namespace firestore { +namespace model { + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ From 6c5a60e9977a739d53f3fdcb1973578626915e67 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 12:19:22 -0500 Subject: [PATCH 04/48] split into files, using crtp, ready to port unit tests --- .../src/firebase/firestore/model/base_path.cc | 124 ----------- .../src/firebase/firestore/model/base_path.h | 44 ++-- .../firebase/firestore/model/field_path.cc | 199 +++++++----------- .../src/firebase/firestore/model/field_path.h | 24 +++ .../firebase/firestore/model/resource_path.cc | 56 +++++ .../firebase/firestore/model/resource_path.h | 48 +++++ 6 files changed, 232 insertions(+), 263 deletions(-) delete mode 100644 Firestore/core/src/firebase/firestore/model/base_path.cc create mode 100644 Firestore/core/src/firebase/firestore/model/resource_path.cc create mode 100644 Firestore/core/src/firebase/firestore/model/resource_path.h diff --git a/Firestore/core/src/firebase/firestore/model/base_path.cc b/Firestore/core/src/firebase/firestore/model/base_path.cc deleted file mode 100644 index b23c390e15d..00000000000 --- a/Firestore/core/src/firebase/firestore/model/base_path.cc +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/firebase/firestore/model/base_path.h" - -#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" - -#include -#include -#include -#include -#include -#include - -#include "absl/strings/str_join.h" -#include "absl/strings/str_replace.h" -#include "absl/strings/str_split.h" - -template -class BasePath { - protected: - using SegmentsT = std::vector; - - public: - using const_iterator = SegmentsT::const_iterator; - - const std::string& operator[](const size_t index) const { - return at(index); - } - - const std::string& at(const size_t index) const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), - "index %u out of range", index); - return segments_[i]; - } - - const std::string& front() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), - "Cannot call front on empty path"); - return at(0); - } - const std::string& back() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), - "Cannot call back on empty path"); - return at(size() - 1); - } - - size_t size() const { - return segments_.size(); - } - - bool empty() const { - return segments_.empty(); - } - - const_iterator begin() const { - return segments_.begin(); - } - const_iterator end() const { - return segments_.end(); - } - - FieldPath Append(const std::string& segment) const { - auto appended = segments_; - appended.push_back(segment); - return FieldPath{std::move(appended)}; - } - - FieldPath Append(const FieldPath& path) const { - auto appended = segments_; - appended.insert(appended.end(), path.begin(), path.end()); - return FieldPath{std::move(appended)}; - } - - FieldPath PopFront(const size_t count = 1) const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - count <= size(), "Cannot call PopFront(%u) on path of length %u", count, - size()); - return FieldPath{segments_.begin() + count, segments_.end()}; - } - - FieldPath PopBack() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !empty(), "Cannot call PopBack() on empty path); - return FieldPath{segments_.begin(), segments_.end() - 1}; - } - - bool IsPrefixOf(const FieldPath& rhs) const { - // OBC empty range - return size() < rhs.size() && - std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); - } - - // std::hash - // to_string - - protected: - BasePath() = default; - template - BasePath(const IterT begin, const IterT end) : segments_{begin, end} { - } - BasePath(std::initializer_list list) - : segments_{list.begin(), list.end()} { - } - FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { - } - ~BasePath() = default; - - private: - SegmentsT segments_; -}; diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index c9da43359a5..3d8c695515e 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -17,9 +17,21 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_BASE_PATH_H_ +#include +#include +#include +#include +#include +#include + +#include "absl/strings/str_join.h" +#include "absl/strings/str_replace.h" +#include "absl/strings/str_split.h" + namespace firebase { namespace firestore { namespace model { +namespace impl { template class BasePath { @@ -65,32 +77,33 @@ class BasePath { return segments_.end(); } - T Append(const std::string& segment) const { - auto appended = segments_; - appended.push_back(segment); - return FieldPath{std::move(appended)}; + T Concatenated(const std::string& segment) const { + auto concatenated = segments_; + concatenated.push_back(segment); + return T{std::move(concatenated)}; } - FieldPath Append(const FieldPath& path) const { - auto appended = segments_; - appended.insert(appended.end(), path.begin(), path.end()); - return FieldPath{std::move(appended)}; + T Concatenated(const T& path) const { + auto concatenated = segments_; + concatenated.insert(concatenated.end(), path.begin(), path.end()); + return T{std::move(concatenated)}; } - FieldPath PopFront(const size_t count = 1) const { + T WithoutFirstElements(const size_t count = 1) const { FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - count <= size(), "Cannot call PopFront(%u) on path of length %u", count, + count <= size(), + "Cannot call WithoutFirstElements(%u) on path of length %u", count, size()); - return FieldPath{segments_.begin() + count, segments_.end()}; + return T{segments_.begin() + count, segments_.end()}; } - FieldPath PopBack() const { + T WithoutLastElement() const { FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !empty(), "Cannot call PopBack() on empty path); - return FieldPath{segments_.begin(), segments_.end() - 1}; + !empty(), "Cannot call WithoutLastElement() on empty path); + return T{segments_.begin(), segments_.end() - 1}; } - bool IsPrefixOf(const FieldPath& rhs) const { + bool IsPrefixOf(const T& rhs) const { // OBC empty range return size() < rhs.size() && std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); @@ -115,6 +128,7 @@ class BasePath { SegmentsT segments_; }; +} // namespace impl } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index b4eec5fec23..5fe674291c5 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -20,10 +20,7 @@ #include #include -#include -#include #include -#include #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" @@ -64,142 +61,96 @@ std::string EscapedSegment(const std::string& segment) { } // namespace -class FieldPath : public BasePath { - public: - FieldPath() = default; - template - FieldPath(const IterT begin, const IterT end) : BasePath{begin, end} { - } - FieldPath(std::initializer_list list) : BasePath{list} { - } +FieldPath FieldPath::ParseServerFormat(const std::string& path) { + // TODO(b/37244157): Once we move to v1beta1, we should make this more + // strict. Right now, it allows non-identifier path components, even if they + // aren't escaped. Technically, this will mangle paths with backticks in + // them used in v1alpha1, but that's fine. - static FieldPath ParseServerFormat(const std::string& path) { - // TODO(b/37244157): Once we move to v1beta1, we should make this more - // strict. Right now, it allows non-identifier path components, even if they - // aren't escaped. Technically, this will mangle paths with backticks in - // them used in v1alpha1, but that's fine. - - SegmentsT segments; - std::string segment; - segment.reserve(path.size()); - - const auto finish_segment = [&segments, &segment] { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !segment.empty(), - "Invalid field path (%s). Paths must not be empty, begin with " - "'.', end with '.', or contain '..'", - path.c_str()); - // Move operation will clear segment, but capacity will remain the same - // (not strictly speaking required by the standard, but true in practice). - segments.push_back(std::move(segment)); - }; - - // Inside backticks, dots are treated literally. - bool insideBackticks = false; - // Whether to treat '\' literally or as an escape character. - bool escapedCharacter = false; - - for (const char c : path) { - if (c == '\0') { - break; - } - if (escapedCharacter) { - escapedCharacter = false; - segment += c; - continue; - } - - switch (c) { - case '.': - if (!insideBackticks) { - finish_segment(); - } else { - segment += c; - } - break; - - case '`': - insideBackticks = !insideBackticks; - break; - - case '\\': - escapedCharacter = true; - break; - - default: - segment += c; - break; - } - } + SegmentsT segments; + std::string segment; + segment.reserve(path.size()); + const auto finish_segment = [&segments, &segment] { FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !insideBackticks, "Unterminated ` in path %s", path.c_str()); - // TODO(b/37244157): Make this a user-facing exception once we - // finalize field escaping. - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !escapedCharacter, "Trailing escape characters not allowed in %s", + !segment.empty(), + "Invalid field path (%s). Paths must not be empty, begin with " + "'.', end with '.', or contain '..'", path.c_str()); + // Move operation will clear segment, but capacity will remain the same + // (not strictly speaking required by the standard, but true in practice). + segments.push_back(std::move(segment)); + }; + + // Inside backticks, dots are treated literally. + bool insideBackticks = false; + // Whether to treat '\' literally or as an escape character. + bool escapedCharacter = false; + + for (const char c : path) { + if (c == '\0') { + break; + } + if (escapedCharacter) { + escapedCharacter = false; + segment += c; + continue; + } - return FieldPath{std::move(segments)}; - } - - std::string CanonicalString() const { - return absl::StrJoin(begin(), end(), '.', - [](std::string* out, const std::string& segment) { - out->append(EscapedSegment(segment)); - }); - } - - // OBC: do we really need emptypath? - // OBC: do we really need *shared* keypath? -}; + switch (c) { + case '.': + if (!insideBackticks) { + finish_segment(); + } else { + segment += c; + } + break; -bool operator<(const FieldPath& lhs, const FieldPath& rhs) { - return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), - rhs.end()); -} + case '`': + insideBackticks = !insideBackticks; + break; -bool operator==(const FieldPath& lhs, const FieldPath& rhs) { - return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), - rhs.end()); -} + case '\\': + escapedCharacter = true; + break; -class ResourcePath : public BasePath { - public: - ResourcePath() = default; - template - ResourcePath(const IterT begin, const IterT end) : BasePath{begin, end} { - } - ResourcePath(std::initializer_list list) : BasePath{list} { + default: + segment += c; + break; + } } - static ResourcePath Parse(const std::string& path) { - // NOTE: The client is ignorant of any path segments containing escape - // sequences (e.g. __id123__) and just passes them through raw (they exist - // for legacy reasons and should not be used frequently). + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !insideBackticks, "Unterminated ` in path %s", path.c_str()); + // TODO(b/37244157): Make this a user-facing exception once we + // finalize field escaping. + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + !escapedCharacter, "Trailing escape characters not allowed in %s", + path.c_str()); - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - path.find("//") == std::string::npos, - "Invalid path (%s). Paths must not contain // in them.", path.c_str()); + return FieldPath{std::move(segments)}; +} - // SkipEmpty because we may still have an empty segment at the beginning or - // end if they had a leading or trailing slash (which we allow). - auto segments = absl::StrSplit(path, '/', absl::SkipEmpty()); - return ResourcePath{std::move(segments)}; - } +std::string FieldPath::CanonicalString() const { + return absl::StrJoin(begin(), end(), '.', + [](std::string* out, const std::string& segment) { + out->append(EscapedSegment(segment)); + }); +} - std::string CanonicalString() const { - // NOTE: The client is ignorant of any path segments containing escape - // sequences (e.g. __id123__) and just passes them through raw (they exist - // for legacy reasons and should not be used frequently). +// OBC: do we really need emptypath? +// OBC: do we really need *shared* keypath? +}; - return absl::StrJoin(begin(), end(), '/'); - } +bool operator<(const FieldPath& lhs, const FieldPath& rhs) { +return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), + rhs.end()); +} - private: - ResourcePath(SegmentsT&& segments) : BasePath{segments} { - } -}; +bool operator==(const FieldPath& lhs, const FieldPath& rhs) { +return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), + rhs.end()); +} } // namespace model } // namespace firestore diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 1798dff36b3..6a2c3a1584a 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -17,10 +17,34 @@ #ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/base_path.h" + namespace firebase { namespace firestore { namespace model { +class FieldPath : public impl::BasePath { + public: + FieldPath() = default; + template + FieldPath(const IterT begin, const IterT end) : BasePath{begin, end} { + } + FieldPath(std::initializer_list list) : BasePath{list} { + } + static FieldPath ParseServerFormat(const std::string& path); + + std::string CanonicalString() const; + + // OBC: do we really need emptypath? + // OBC: do we really need *shared* keypath? +}; + +bool operator<(const FieldPath& lhs, const FieldPath& rhs); +bool operator==(const FieldPath& lhs, const FieldPath& rhs); + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc new file mode 100644 index 00000000000..78a56dcd255 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -0,0 +1,56 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/field_path.h" + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +#include +#include + +#include "absl/strings/str_join.h" +#include "absl/strings/str_split.h" + +namespace firebase { +namespace firestore { +namespace model { + +static ResourcePath ResourcePath::Parse(const std::string& path) { + // NOTE: The client is ignorant of any path segments containing escape + // sequences (e.g. __id123__) and just passes them through raw (they exist + // for legacy reasons and should not be used frequently). + + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + path.find("//") == std::string::npos, + "Invalid path (%s). Paths must not contain // in them.", path.c_str()); + + // SkipEmpty because we may still have an empty segment at the beginning or + // end if they had a leading or trailing slash (which we allow). + auto segments = absl::StrSplit(path, '/', absl::SkipEmpty()); + return ResourcePath{std::move(segments)}; +} + +std::string ResourcePath::CanonicalString() const { + // NOTE: The client is ignorant of any path segments containing escape + // sequences (e.g. __id123__) and just passes them through raw (they exist + // for legacy reasons and should not be used frequently). + + return absl::StrJoin(begin(), end(), '/'); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h new file mode 100644 index 00000000000..5a7e0821d7d --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -0,0 +1,48 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ + +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/base_path.h" + +namespace firebase { +namespace firestore { +namespace model { + +class ResourcePath : public impl::BasePath { + public: + ResourcePath() = default; + template + ResourcePath(const IterT begin, const IterT end) : BasePath{begin, end} { + } + ResourcePath(std::initializer_list list) : BasePath{list} { + } + static ResourcePath Parse(const std::string& path); + + std::string CanonicalString() const; + + private: + ResourcePath(SegmentsT&& segments) : BasePath{segments} { + } +}; + +} // namespace model +} // namespace firestore +} // namespace firebase From 880b11576592dd0b4496e2307c5de98307190b88 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 13:50:26 -0500 Subject: [PATCH 05/48] add unit test file for base path --- .../firestore/model/base_path_test.cc | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 Firestore/core/test/firebase/firestore/model/base_path_test.cc diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc new file mode 100644 index 00000000000..78e5eea8ee7 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -0,0 +1,49 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/base_path.h" + +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +struct Path : impl::BasePath { +}; + +TEST(BasePath, Constructor) { + const Path path; + // EXPECT_EQ(0, timestamp_zero.seconds()); +} + +// constructor +// indexing +// WithoutFirst +// WithoutLast +// Concatenated +// < +// isPrefixOf + +// throws on invalid +// canonical string +// --//-- of substr? + +} // namespace model +} // namespace firestore +} // namespace firebase From 507687267ea526d4bdee05078eaf2d16a92e51e4 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 13:50:37 -0500 Subject: [PATCH 06/48] add new files to projects --- Firestore/Example/Firestore.xcodeproj/project.pbxproj | 6 ++++++ Firestore/core/src/firebase/firestore/model/CMakeLists.txt | 3 +++ Firestore/core/test/firebase/firestore/model/CMakeLists.txt | 1 + .../third_party/abseil-cpp/absl/strings/CMakeLists.txt | 6 ++++++ 4 files changed, 16 insertions(+) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 1d397035404..84c7ddb4f5e 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -82,6 +82,8 @@ ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; }; ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; + B69E7F1120239853003C7930 /* base_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B69E7F1020239852003C7930 /* base_path_test.cc */; }; + B69E7F122023985A003C7930 /* base_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B69E7F1020239852003C7930 /* base_path_test.cc */; }; C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; }; D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; }; D5B25474286C9800CE42B8C2 /* FSTTestDispatchQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */; }; @@ -270,6 +272,7 @@ ABF341021FE8593500C48322 /* FSTAPIHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTAPIHelpers.m; sourceTree = ""; }; ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B69E7F1020239852003C7930 /* base_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base_path_test.cc; sourceTree = ""; }; CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = ""; }; D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSTTestDispatchQueue.m; sourceTree = ""; }; @@ -553,6 +556,7 @@ AB356EF5200E9D1A0089B766 /* model */ = { isa = PBXGroup; children = ( + B69E7F1020239852003C7930 /* base_path_test.cc */, AB356EF6200EA5EB0089B766 /* field_value_test.cc */, ABF6506B201131F8005F2C74 /* timestamp_test.cc */, AB71064B201FA60300344F18 /* database_id_test.cc */, @@ -1216,6 +1220,7 @@ 6003F59E195388D20070C39A /* FIRAppDelegate.m in Sources */, 6003F5A7195388D20070C39A /* FIRViewController.m in Sources */, 6003F59A195388D20070C39A /* main.m in Sources */, + B69E7F1120239853003C7930 /* base_path_test.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1250,6 +1255,7 @@ DE51B1D41F0D48CD0013853F /* FSTViewTests.m in Sources */, 54740A581FC914F000713A1A /* autoid_test.cc in Sources */, DE51B1F41F0D491B0013853F /* FSTRemoteEventTests.m in Sources */, + B69E7F122023985A003C7930 /* base_path_test.cc in Sources */, 548DB927200D590300E00ABC /* assert_test.cc in Sources */, 54E928241F33953300C1953E /* FSTEventAccumulator.m in Sources */, 5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */, diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index aee0d86a3cd..8bdbe18d4e5 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -15,8 +15,11 @@ cc_library( firebase_firestore_model SOURCES + base_path.h database_id.cc database_id.h + field_path.cc + field_path.h field_value.cc field_value.h timestamp.cc diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 0f83bf2e1e2..81c91666705 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -15,6 +15,7 @@ cc_test( firebase_firestore_model_test SOURCES + base_path_test.cc database_id_test.cc field_value_test.cc timestamp_test.cc diff --git a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt index 070bf4f4b63..84cd5c8d9f0 100644 --- a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt +++ b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt @@ -18,6 +18,9 @@ list(APPEND STRINGS_PUBLIC_HEADERS "ascii.h" "match.h" + "str_join.h" + "str_replace.h" + "str_split.h" "string_view.h" ) @@ -34,6 +37,9 @@ list(APPEND STRINGS_SRC "ascii.cc" "internal/memutil.cc" "match.cc" + "str_join.cc" + "str_replace.cc" + "str_split.cc" "string_view.cc" ${STRINGS_PUBLIC_HEADERS} ${STRINGS_INTERNAL_HEADERS} From 0b49e357135b8c3595ce1ee512931883d7ccdd40 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 14:01:12 -0500 Subject: [PATCH 07/48] add abseil files --- .../Firestore.xcodeproj/project.pbxproj | 2 - .../abseil-cpp/absl/strings/str_join.cc | 1 + .../abseil-cpp/absl/strings/str_join.h | 288 ++++++ .../abseil-cpp/absl/strings/str_join_test.cc | 473 +++++++++ .../abseil-cpp/absl/strings/str_replace.cc | 79 ++ .../abseil-cpp/absl/strings/str_replace.h | 213 ++++ .../absl/strings/str_replace_test.cc | 341 +++++++ .../abseil-cpp/absl/strings/str_split.cc | 136 +++ .../abseil-cpp/absl/strings/str_split.h | 511 ++++++++++ .../abseil-cpp/absl/strings/str_split_test.cc | 907 ++++++++++++++++++ 10 files changed, 2949 insertions(+), 2 deletions(-) create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_join.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_join.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_join_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_replace.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_replace.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_replace_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_split.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_split.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_split_test.cc diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 84c7ddb4f5e..e3a49365661 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -82,7 +82,6 @@ ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; }; ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; - B69E7F1120239853003C7930 /* base_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B69E7F1020239852003C7930 /* base_path_test.cc */; }; B69E7F122023985A003C7930 /* base_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B69E7F1020239852003C7930 /* base_path_test.cc */; }; C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; }; D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; }; @@ -1220,7 +1219,6 @@ 6003F59E195388D20070C39A /* FIRAppDelegate.m in Sources */, 6003F5A7195388D20070C39A /* FIRViewController.m in Sources */, 6003F59A195388D20070C39A /* main.m in Sources */, - B69E7F1120239853003C7930 /* base_path_test.cc in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_join.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_join.cc new file mode 100644 index 00000000000..929c05d7236 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_join.cc @@ -0,0 +1 @@ +404: Not Found diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_join.h b/Firestore/third_party/abseil-cpp/absl/strings/str_join.h new file mode 100644 index 00000000000..47337490d0b --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_join.h @@ -0,0 +1,288 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: str_join.h +// ----------------------------------------------------------------------------- +// +// This header file contains functions for joining a range of elements and +// returning the result as a std::string. StrJoin operations are specified by passing +// a range, a separator std::string to use between the elements joined, and an +// optional Formatter responsible for converting each argument in the range to a +// std::string. If omitted, a default `AlphaNumFormatter()` is called on the elements +// to be joined, using the same formatting that `absl::StrCat()` uses. This +// package defines a number of default formatters, and you can define your own +// implementations. +// +// Ranges are specified by passing a container with `std::begin()` and +// `std::end()` iterators, container-specific `begin()` and `end()` iterators, a +// brace-initialized `std::initializer_list`, or a `std::tuple` of heterogeneous +// objects. The separator std::string is specified as an `absl::string_view`. +// +// Because the default formatter uses the `absl::AlphaNum` class, +// `absl::StrJoin()`, like `absl::StrCat()`, will work out-of-the-box on +// collections of strings, ints, floats, doubles, etc. +// +// Example: +// +// std::vector v = {"foo", "bar", "baz"}; +// std::string s = absl::StrJoin(v, "-"); +// EXPECT_EQ("foo-bar-baz", s); +// +// See comments on the `absl::StrJoin()` function for more examples. + +#ifndef ABSL_STRINGS_STR_JOIN_H_ +#define ABSL_STRINGS_STR_JOIN_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/strings/internal/str_join_internal.h" +#include "absl/strings/string_view.h" + +namespace absl { + +// ----------------------------------------------------------------------------- +// Concept: Formatter +// ----------------------------------------------------------------------------- +// +// A Formatter is a function object that is responsible for formatting its +// argument as a std::string and appending it to a given output std::string. Formatters +// may be implemented as function objects, lambdas, or normal functions. You may +// provide your own Formatter to enable `absl::StrJoin()` to work with arbitrary +// types. +// +// The following is an example of a custom Formatter that simply uses +// `std::to_string()` to format an integer as a std::string. +// +// struct MyFormatter { +// void operator()(std::string* out, int i) const { +// out->append(std::to_string(i)); +// } +// }; +// +// You would use the above formatter by passing an instance of it as the final +// argument to `absl::StrJoin()`: +// +// std::vector v = {1, 2, 3, 4}; +// std::string s = absl::StrJoin(v, "-", MyFormatter()); +// EXPECT_EQ("1-2-3-4", s); +// +// The following standard formatters are provided within this file: +// +// - `AlphaNumFormatter()` (the default) +// - `StreamFormatter()` +// - `PairFormatter()` +// - `DereferenceFormatter()` + +// AlphaNumFormatter() +// +// Default formatter used if none is specified. Uses `absl::AlphaNum` to convert +// numeric arguments to strings. +inline strings_internal::AlphaNumFormatterImpl AlphaNumFormatter() { + return strings_internal::AlphaNumFormatterImpl(); +} + +// StreamFormatter() +// +// Formats its argument using the << operator. +inline strings_internal::StreamFormatterImpl StreamFormatter() { + return strings_internal::StreamFormatterImpl(); +} + +// Function Template: PairFormatter(Formatter, absl::string_view, Formatter) +// +// Formats a `std::pair` by putting a given separator between the pair's +// `.first` and `.second` members. This formatter allows you to specify +// custom Formatters for both the first and second member of each pair. +template +inline strings_internal::PairFormatterImpl +PairFormatter(FirstFormatter f1, absl::string_view sep, SecondFormatter f2) { + return strings_internal::PairFormatterImpl( + std::move(f1), sep, std::move(f2)); +} + +// Function overload of PairFormatter() for using a default +// `AlphaNumFormatter()` for each Formatter in the pair. +inline strings_internal::PairFormatterImpl< + strings_internal::AlphaNumFormatterImpl, + strings_internal::AlphaNumFormatterImpl> +PairFormatter(absl::string_view sep) { + return PairFormatter(AlphaNumFormatter(), sep, AlphaNumFormatter()); +} + +// Function Template: DereferenceFormatter(Formatter) +// +// Formats its argument by dereferencing it and then applying the given +// formatter. This formatter is useful for formatting a container of +// pointer-to-T. This pattern often shows up when joining repeated fields in +// protocol buffers. +template +strings_internal::DereferenceFormatterImpl DereferenceFormatter( + Formatter&& f) { + return strings_internal::DereferenceFormatterImpl( + std::forward(f)); +} + +// Function overload of `DererefenceFormatter()` for using a default +// `AlphaNumFormatter()`. +inline strings_internal::DereferenceFormatterImpl< + strings_internal::AlphaNumFormatterImpl> +DereferenceFormatter() { + return strings_internal::DereferenceFormatterImpl< + strings_internal::AlphaNumFormatterImpl>(AlphaNumFormatter()); +} + +// ----------------------------------------------------------------------------- +// StrJoin() +// ----------------------------------------------------------------------------- +// +// Joins a range of elements and returns the result as a std::string. +// `absl::StrJoin()` takes a range, a separator std::string to use between the +// elements joined, and an optional Formatter responsible for converting each +// argument in the range to a std::string. +// +// If omitted, the default `AlphaNumFormatter()` is called on the elements to be +// joined. +// +// Example 1: +// // Joins a collection of strings. This pattern also works with a collection +// // of `asbl::string_view` or even `const char*`. +// std::vector v = {"foo", "bar", "baz"}; +// std::string s = absl::StrJoin(v, "-"); +// EXPECT_EQ("foo-bar-baz", s); +// +// Example 2: +// // Joins the values in the given `std::initializer_list<>` specified using +// // brace initialization. This pattern also works with an initializer_list +// // of ints or `absl::string_view` -- any `AlphaNum`-compatible type. +// std::string s = absl::StrJoin({"foo", "bar", "baz"}, "-"); +// EXPECT_EQ("foo-bar-baz", s); +// +// Example 3: +// // Joins a collection of ints. This pattern also works with floats, +// // doubles, int64s -- any `StrCat()`-compatible type. +// std::vector v = {1, 2, 3, -4}; +// std::string s = absl::StrJoin(v, "-"); +// EXPECT_EQ("1-2-3--4", s); +// +// Example 4: +// // Joins a collection of pointer-to-int. By default, pointers are +// // dereferenced and the pointee is formatted using the default format for +// // that type; such dereferencing occurs for all levels of indirection, so +// // this pattern works just as well for `std::vector` as for +// // `std::vector`. +// int x = 1, y = 2, z = 3; +// std::vector v = {&x, &y, &z}; +// std::string s = absl::StrJoin(v, "-"); +// EXPECT_EQ("1-2-3", s); +// +// Example 5: +// // Dereferencing of `std::unique_ptr<>` is also supported: +// std::vector> v +// v.emplace_back(new int(1)); +// v.emplace_back(new int(2)); +// v.emplace_back(new int(3)); +// std::string s = absl::StrJoin(v, "-"); +// EXPECT_EQ("1-2-3", s); +// +// Example 6: +// // Joins a `std::map`, with each key-value pair separated by an equals +// // sign. This pattern would also work with, say, a +// // `std::vector>`. +// std::map m = { +// std::make_pair("a", 1), +// std::make_pair("b", 2), +// std::make_pair("c", 3)}; +// std::string s = absl::StrJoin(m, ",", absl::PairFormatter("=")); +// EXPECT_EQ("a=1,b=2,c=3", s); +// +// Example 7: +// // These examples show how `absl::StrJoin()` handles a few common edge +// // cases: +// std::vector v_empty; +// EXPECT_EQ("", absl::StrJoin(v_empty, "-")); +// +// std::vector v_one_item = {"foo"}; +// EXPECT_EQ("foo", absl::StrJoin(v_one_item, "-")); +// +// std::vector v_empty_string = {""}; +// EXPECT_EQ("", absl::StrJoin(v_empty_string, "-")); +// +// std::vector v_one_item_empty_string = {"a", ""}; +// EXPECT_EQ("a-", absl::StrJoin(v_one_item_empty_string, "-")); +// +// std::vector v_two_empty_string = {"", ""}; +// EXPECT_EQ("-", absl::StrJoin(v_two_empty_string, "-")); +// +// Example 8: +// // Joins a `std::tuple` of heterogeneous types, converting each to +// // a std::string using the `absl::AlphaNum` class. +// std::string s = absl::StrJoin(std::make_tuple(123, "abc", 0.456), "-"); +// EXPECT_EQ("123-abc-0.456", s); + +template +std::string StrJoin(Iterator start, Iterator end, absl::string_view sep, + Formatter&& fmt) { + return strings_internal::JoinAlgorithm(start, end, sep, fmt); +} + +template +std::string StrJoin(const Range& range, absl::string_view separator, + Formatter&& fmt) { + return strings_internal::JoinRange(range, separator, fmt); +} + +template +std::string StrJoin(std::initializer_list il, absl::string_view separator, + Formatter&& fmt) { + return strings_internal::JoinRange(il, separator, fmt); +} + +template +std::string StrJoin(const std::tuple& value, absl::string_view separator, + Formatter&& fmt) { + return strings_internal::JoinAlgorithm(value, separator, fmt); +} + +template +std::string StrJoin(Iterator start, Iterator end, absl::string_view separator) { + return strings_internal::JoinRange(start, end, separator); +} + +template +std::string StrJoin(const Range& range, absl::string_view separator) { + return strings_internal::JoinRange(range, separator); +} + +template +std::string StrJoin(std::initializer_list il, absl::string_view separator) { + return strings_internal::JoinRange(il, separator); +} + +template +std::string StrJoin(const std::tuple& value, absl::string_view separator) { + return strings_internal::JoinAlgorithm(value, separator, AlphaNumFormatter()); +} + +} // namespace absl + +#endif // ABSL_STRINGS_STR_JOIN_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_join_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_join_test.cc new file mode 100644 index 00000000000..03b60f03c82 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_join_test.cc @@ -0,0 +1,473 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Unit tests for all join.h functions + +#include "absl/strings/str_join.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/base/macros.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" + +namespace { + +TEST(StrJoin, APIExamples) { + { + // Collection of strings + std::vector v = {"foo", "bar", "baz"}; + EXPECT_EQ("foo-bar-baz", absl::StrJoin(v, "-")); + } + + { + // Collection of absl::string_view + std::vector v = {"foo", "bar", "baz"}; + EXPECT_EQ("foo-bar-baz", absl::StrJoin(v, "-")); + } + + { + // Collection of const char* + std::vector v = {"foo", "bar", "baz"}; + EXPECT_EQ("foo-bar-baz", absl::StrJoin(v, "-")); + } + + { + // Collection of non-const char* + std::string a = "foo", b = "bar", c = "baz"; + std::vector v = {&a[0], &b[0], &c[0]}; + EXPECT_EQ("foo-bar-baz", absl::StrJoin(v, "-")); + } + + { + // Collection of ints + std::vector v = {1, 2, 3, -4}; + EXPECT_EQ("1-2-3--4", absl::StrJoin(v, "-")); + } + + { + // Literals passed as a std::initializer_list + std::string s = absl::StrJoin({"a", "b", "c"}, "-"); + EXPECT_EQ("a-b-c", s); + } + { + // Join a std::tuple. + std::string s = absl::StrJoin(std::make_tuple(123, "abc", 0.456), "-"); + EXPECT_EQ("123-abc-0.456", s); + } + + { + // Collection of unique_ptrs + std::vector> v; + v.emplace_back(new int(1)); + v.emplace_back(new int(2)); + v.emplace_back(new int(3)); + EXPECT_EQ("1-2-3", absl::StrJoin(v, "-")); + } + + { + // Array of ints + const int a[] = {1, 2, 3, -4}; + EXPECT_EQ("1-2-3--4", absl::StrJoin(a, a + ABSL_ARRAYSIZE(a), "-")); + } + + { + // Collection of pointers + int x = 1, y = 2, z = 3; + std::vector v = {&x, &y, &z}; + EXPECT_EQ("1-2-3", absl::StrJoin(v, "-")); + } + + { + // Collection of pointers to pointers + int x = 1, y = 2, z = 3; + int *px = &x, *py = &y, *pz = &z; + std::vector v = {&px, &py, &pz}; + EXPECT_EQ("1-2-3", absl::StrJoin(v, "-")); + } + + { + // Collection of pointers to std::string + std::string a("a"), b("b"); + std::vector v = {&a, &b}; + EXPECT_EQ("a-b", absl::StrJoin(v, "-")); + } + + { + // A std::map, which is a collection of std::pair<>s. + std::map m = { {"a", 1}, {"b", 2}, {"c", 3} }; + EXPECT_EQ("a=1,b=2,c=3", absl::StrJoin(m, ",", absl::PairFormatter("="))); + } + + { + // Shows absl::StrSplit and absl::StrJoin working together. This example is + // equivalent to s/=/-/g. + const std::string s = "a=b=c=d"; + EXPECT_EQ("a-b-c-d", absl::StrJoin(absl::StrSplit(s, "="), "-")); + } + + // + // A few examples of edge cases + // + + { + // Empty range yields an empty std::string. + std::vector v; + EXPECT_EQ("", absl::StrJoin(v, "-")); + } + + { + // A range of 1 element gives a std::string with that element but no separator. + std::vector v = {"foo"}; + EXPECT_EQ("foo", absl::StrJoin(v, "-")); + } + + { + // A range with a single empty std::string element + std::vector v = {""}; + EXPECT_EQ("", absl::StrJoin(v, "-")); + } + + { + // A range with 2 elements, one of which is an empty std::string + std::vector v = {"a", ""}; + EXPECT_EQ("a-", absl::StrJoin(v, "-")); + } + + { + // A range with 2 empty elements. + std::vector v = {"", ""}; + EXPECT_EQ("-", absl::StrJoin(v, "-")); + } + + { + // A std::vector of bool. + std::vector v = {true, false, true}; + EXPECT_EQ("1-0-1", absl::StrJoin(v, "-")); + } +} + +TEST(StrJoin, CustomFormatter) { + std::vector v{"One", "Two", "Three"}; + { + std::string joined = absl::StrJoin(v, "", [](std::string* out, const std::string& in) { + absl::StrAppend(out, "(", in, ")"); + }); + EXPECT_EQ("(One)(Two)(Three)", joined); + } + { + class ImmovableFormatter { + public: + void operator()(std::string* out, const std::string& in) { + absl::StrAppend(out, "(", in, ")"); + } + ImmovableFormatter() {} + ImmovableFormatter(const ImmovableFormatter&) = delete; + }; + EXPECT_EQ("(One)(Two)(Three)", absl::StrJoin(v, "", ImmovableFormatter())); + } + { + class OverloadedFormatter { + public: + void operator()(std::string* out, const std::string& in) { + absl::StrAppend(out, "(", in, ")"); + } + void operator()(std::string* out, const std::string& in) const { + absl::StrAppend(out, "[", in, "]"); + } + }; + EXPECT_EQ("(One)(Two)(Three)", absl::StrJoin(v, "", OverloadedFormatter())); + const OverloadedFormatter fmt = {}; + EXPECT_EQ("[One][Two][Three]", absl::StrJoin(v, "", fmt)); + } +} + +// +// Tests the Formatters +// + +TEST(AlphaNumFormatter, FormatterAPI) { + // Not an exhaustive test. See strings/strcat_test.h for the exhaustive test + // of what AlphaNum can convert. + auto f = absl::AlphaNumFormatter(); + std::string s; + f(&s, "Testing: "); + f(&s, static_cast(1)); + f(&s, static_cast(2)); + f(&s, static_cast(3)); + f(&s, static_cast(4)); + f(&s, static_cast(5)); + f(&s, static_cast(6)); + f(&s, static_cast(7)); + f(&s, absl::string_view(" OK")); + EXPECT_EQ("Testing: 1234567 OK", s); +} + +// Make sure people who are mistakenly using std::vector even though +// they're not memory-constrained can use absl::AlphaNumFormatter(). +TEST(AlphaNumFormatter, VectorOfBool) { + auto f = absl::AlphaNumFormatter(); + std::string s; + std::vector v = {true, false, true}; + f(&s, *v.cbegin()); + f(&s, *v.begin()); + f(&s, v[1]); + EXPECT_EQ("110", s); +} + +TEST(AlphaNumFormatter, AlphaNum) { + auto f = absl::AlphaNumFormatter(); + std::string s; + f(&s, absl::AlphaNum("hello")); + EXPECT_EQ("hello", s); +} + +struct StreamableType { + std::string contents; +}; +inline std::ostream& operator<<(std::ostream& os, const StreamableType& t) { + os << "Streamable:" << t.contents; + return os; +} + +TEST(StreamFormatter, FormatterAPI) { + auto f = absl::StreamFormatter(); + std::string s; + f(&s, "Testing: "); + f(&s, static_cast(1)); + f(&s, static_cast(2)); + f(&s, static_cast(3)); + f(&s, static_cast(4)); + f(&s, static_cast(5)); + f(&s, static_cast(6)); + f(&s, static_cast(7)); + f(&s, absl::string_view(" OK ")); + StreamableType streamable = {"object"}; + f(&s, streamable); + EXPECT_EQ("Testing: 1234567 OK Streamable:object", s); +} + +// A dummy formatter that wraps each element in parens. Used in some tests +// below. +struct TestingParenFormatter { + template + void operator()(std::string* s, const T& t) { + absl::StrAppend(s, "(", t, ")"); + } +}; + +TEST(PairFormatter, FormatterAPI) { + { + // Tests default PairFormatter(sep) that uses AlphaNumFormatter for the + // 'first' and 'second' members. + const auto f = absl::PairFormatter("="); + std::string s; + f(&s, std::make_pair("a", "b")); + f(&s, std::make_pair(1, 2)); + EXPECT_EQ("a=b1=2", s); + } + + { + // Tests using a custom formatter for the 'first' and 'second' members. + auto f = absl::PairFormatter(TestingParenFormatter(), "=", + TestingParenFormatter()); + std::string s; + f(&s, std::make_pair("a", "b")); + f(&s, std::make_pair(1, 2)); + EXPECT_EQ("(a)=(b)(1)=(2)", s); + } +} + +TEST(DereferenceFormatter, FormatterAPI) { + { + // Tests wrapping the default AlphaNumFormatter. + const absl::strings_internal::DereferenceFormatterImpl< + absl::strings_internal::AlphaNumFormatterImpl> + f; + int x = 1, y = 2, z = 3; + std::string s; + f(&s, &x); + f(&s, &y); + f(&s, &z); + EXPECT_EQ("123", s); + } + + { + // Tests wrapping std::string's default formatter. + absl::strings_internal::DereferenceFormatterImpl< + absl::strings_internal::DefaultFormatter::Type> + f; + + std::string x = "x"; + std::string y = "y"; + std::string z = "z"; + std::string s; + f(&s, &x); + f(&s, &y); + f(&s, &z); + EXPECT_EQ(s, "xyz"); + } + + { + // Tests wrapping a custom formatter. + auto f = absl::DereferenceFormatter(TestingParenFormatter()); + int x = 1, y = 2, z = 3; + std::string s; + f(&s, &x); + f(&s, &y); + f(&s, &z); + EXPECT_EQ("(1)(2)(3)", s); + } + + { + absl::strings_internal::DereferenceFormatterImpl< + absl::strings_internal::AlphaNumFormatterImpl> + f; + auto x = std::unique_ptr(new int(1)); + auto y = std::unique_ptr(new int(2)); + auto z = std::unique_ptr(new int(3)); + std::string s; + f(&s, x); + f(&s, y); + f(&s, z); + EXPECT_EQ("123", s); + } +} + +// +// Tests the interfaces for the 4 public Join function overloads. The semantics +// of the algorithm is covered in the above APIExamples test. +// +TEST(StrJoin, PublicAPIOverloads) { + std::vector v = {"a", "b", "c"}; + + // Iterators + formatter + EXPECT_EQ("a-b-c", + absl::StrJoin(v.begin(), v.end(), "-", absl::AlphaNumFormatter())); + // Range + formatter + EXPECT_EQ("a-b-c", absl::StrJoin(v, "-", absl::AlphaNumFormatter())); + // Iterators, no formatter + EXPECT_EQ("a-b-c", absl::StrJoin(v.begin(), v.end(), "-")); + // Range, no formatter + EXPECT_EQ("a-b-c", absl::StrJoin(v, "-")); +} + +TEST(StrJoin, Array) { + const absl::string_view a[] = {"a", "b", "c"}; + EXPECT_EQ("a-b-c", absl::StrJoin(a, "-")); +} + +TEST(StrJoin, InitializerList) { + { EXPECT_EQ("a-b-c", absl::StrJoin({"a", "b", "c"}, "-")); } + + { + auto a = {"a", "b", "c"}; + EXPECT_EQ("a-b-c", absl::StrJoin(a, "-")); + } + + { + std::initializer_list a = {"a", "b", "c"}; + EXPECT_EQ("a-b-c", absl::StrJoin(a, "-")); + } + + { + std::initializer_list a = {"a", "b", "c"}; + EXPECT_EQ("a-b-c", absl::StrJoin(a, "-")); + } + + { + std::initializer_list a = {"a", "b", "c"}; + EXPECT_EQ("a-b-c", absl::StrJoin(a, "-")); + } + + { + // Tests initializer_list with a non-default formatter + auto a = {"a", "b", "c"}; + TestingParenFormatter f; + EXPECT_EQ("(a)-(b)-(c)", absl::StrJoin(a, "-", f)); + } + + { + // initializer_list of ints + EXPECT_EQ("1-2-3", absl::StrJoin({1, 2, 3}, "-")); + } + + { + // Tests initializer_list of ints with a non-default formatter + auto a = {1, 2, 3}; + TestingParenFormatter f; + EXPECT_EQ("(1)-(2)-(3)", absl::StrJoin(a, "-", f)); + } +} + +TEST(StrJoin, Tuple) { + EXPECT_EQ("", absl::StrJoin(std::make_tuple(), "-")); + EXPECT_EQ("hello", absl::StrJoin(std::make_tuple("hello"), "-")); + + int x(10); + std::string y("hello"); + double z(3.14); + EXPECT_EQ("10-hello-3.14", absl::StrJoin(std::make_tuple(x, y, z), "-")); + + // Faster! Faster!! + EXPECT_EQ("10-hello-3.14", + absl::StrJoin(std::make_tuple(x, std::cref(y), z), "-")); + + struct TestFormatter { + char buffer[128]; + void operator()(std::string* out, int v) { + snprintf(buffer, sizeof(buffer), "%#.8x", v); + out->append(buffer); + } + void operator()(std::string* out, double v) { + snprintf(buffer, sizeof(buffer), "%#.0f", v); + out->append(buffer); + } + void operator()(std::string* out, const std::string& v) { + snprintf(buffer, sizeof(buffer), "%.4s", v.c_str()); + out->append(buffer); + } + }; + EXPECT_EQ("0x0000000a-hell-3.", + absl::StrJoin(std::make_tuple(x, y, z), "-", TestFormatter())); + EXPECT_EQ( + "0x0000000a-hell-3.", + absl::StrJoin(std::make_tuple(x, std::cref(y), z), "-", TestFormatter())); + EXPECT_EQ("0x0000000a-hell-3.", + absl::StrJoin(std::make_tuple(&x, &y, &z), "-", + absl::DereferenceFormatter(TestFormatter()))); + EXPECT_EQ("0x0000000a-hell-3.", + absl::StrJoin(std::make_tuple(absl::make_unique(x), + absl::make_unique(y), + absl::make_unique(z)), + "-", absl::DereferenceFormatter(TestFormatter()))); + EXPECT_EQ("0x0000000a-hell-3.", + absl::StrJoin(std::make_tuple(absl::make_unique(x), &y, &z), + "-", absl::DereferenceFormatter(TestFormatter()))); +} + +} // namespace diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_replace.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_replace.cc new file mode 100644 index 00000000000..69efa357138 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_replace.cc @@ -0,0 +1,79 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/str_replace.h" + +#include "absl/strings/str_cat.h" + +namespace absl { +namespace strings_internal { + +using FixedMapping = + std::initializer_list>; + +// Applies the ViableSubstitutions in subs_ptr to the absl::string_view s, and +// stores the result in *result_ptr. Returns the number of substitutions that +// occurred. +int ApplySubstitutions( + absl::string_view s, + std::vector* subs_ptr, + std::string* result_ptr) { + auto& subs = *subs_ptr; + int substitutions = 0; + size_t pos = 0; + while (!subs.empty()) { + auto& sub = subs.back(); + if (sub.offset >= pos) { + if (pos <= s.size()) { + StrAppend(result_ptr, s.substr(pos, sub.offset - pos), sub.replacement); + } + pos = sub.offset + sub.old.size(); + substitutions += 1; + } + sub.offset = s.find(sub.old, pos); + if (sub.offset == s.npos) { + subs.pop_back(); + } else { + // Insertion sort to ensure the last ViableSubstitution continues to be + // before all the others. + size_t index = subs.size(); + while (--index && subs[index - 1].OccursBefore(subs[index])) { + std::swap(subs[index], subs[index - 1]); + } + } + } + result_ptr->append(s.data() + pos, s.size() - pos); + return substitutions; +} + +} // namespace strings_internal + +// We can implement this in terms of the generic StrReplaceAll, but +// we must specify the template overload because C++ cannot deduce the type +// of an initializer_list parameter to a function, and also if we don't specify +// the type, we just call ourselves. +// +// Note that we implement them here, rather than in the header, so that they +// aren't inlined. + +std::string StrReplaceAll(absl::string_view s, + strings_internal::FixedMapping replacements) { + return StrReplaceAll(s, replacements); +} + +int StrReplaceAll(strings_internal::FixedMapping replacements, std::string* target) { + return StrReplaceAll(replacements, target); +} + +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_replace.h b/Firestore/third_party/abseil-cpp/absl/strings/str_replace.h new file mode 100644 index 00000000000..f4d9bb9545d --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_replace.h @@ -0,0 +1,213 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: str_replace.h +// ----------------------------------------------------------------------------- +// +// This file defines `absl::StrReplaceAll()`, a general-purpose std::string +// replacement function designed for large, arbitrary text substitutions, +// especially on strings which you are receiving from some other system for +// further processing (e.g. processing regular expressions, escaping HTML +// entities, etc. `StrReplaceAll` is designed to be efficient even when only +// one substitution is being performed, or when substitution is rare. +// +// If the std::string being modified is known at compile-time, and the substitutions +// vary, `absl::Substitute()` may be a better choice. +// +// Example: +// +// std::string html_escaped = absl::StrReplaceAll(user_input, { +// {"&", "&"}, +// {"<", "<"}, +// {">", ">"}, +// {"\"", """}, +// {"'", "'"}}); +#ifndef ABSL_STRINGS_STR_REPLACE_H_ +#define ABSL_STRINGS_STR_REPLACE_H_ + +#include +#include +#include + +#include "absl/base/attributes.h" +#include "absl/strings/string_view.h" + +namespace absl { + +// StrReplaceAll() +// +// Replaces character sequences within a given std::string with replacements provided +// within an initializer list of key/value pairs. Candidate replacements are +// considered in order as they occur within the std::string, with earlier matches +// taking precedence, and longer matches taking precedence for candidates +// starting at the same position in the std::string. Once a substitution is made, the +// replaced text is not considered for any further substitutions. +// +// Example: +// +// std::string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", +// {{"$count", absl::StrCat(5)}, +// {"$who", "Bob"}, +// {"#Noun", "Apples"}}); +// EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); +ABSL_MUST_USE_RESULT std::string StrReplaceAll( + absl::string_view s, + std::initializer_list> + replacements); + +// Overload of `StrReplaceAll()` to accept a container of key/value replacement +// pairs (typically either an associative map or a `std::vector` of `std::pair` +// elements). A vector of pairs is generally more efficient. +// +// Examples: +// +// std::map replacements; +// replacements["$who"] = "Bob"; +// replacements["$count"] = "5"; +// replacements["#Noun"] = "Apples"; +// std::string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", +// replacements); +// EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); +// +// // A std::vector of std::pair elements can be more efficient. +// std::vector> replacements; +// replacements.push_back({"&", "&"}); +// replacements.push_back({"<", "<"}); +// replacements.push_back({">", ">"}); +// std::string s = absl::StrReplaceAll("if (ptr < &foo)", +// replacements); +// EXPECT_EQ("if (ptr < &foo)", s); +template +std::string StrReplaceAll(absl::string_view s, const StrToStrMapping& replacements); + +// Overload of `StrReplaceAll()` to replace character sequences within a given +// output std::string *in place* with replacements provided within an initializer +// list of key/value pairs, returning the number of substitutions that occurred. +// +// Example: +// +// std::string s = std::string("$who bought $count #Noun. Thanks $who!"); +// int count; +// count = absl::StrReplaceAll({{"$count", absl::StrCat(5)}, +// {"$who", "Bob"}, +// {"#Noun", "Apples"}}, &s); +// EXPECT_EQ(count, 4); +// EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); +int StrReplaceAll( + std::initializer_list> + replacements, + std::string* target); + +// Overload of `StrReplaceAll()` to replace patterns within a given output +// std::string *in place* with replacements provided within a container of key/value +// pairs. +// +// Example: +// +// std::string s = std::string("if (ptr < &foo)"); +// int count = absl::StrReplaceAll({{"&", "&"}, +// {"<", "<"}, +// {">", ">"}}, &s); +// EXPECT_EQ(count, 2); +// EXPECT_EQ("if (ptr < &foo)", s); +template +int StrReplaceAll(const StrToStrMapping& replacements, std::string* target); + +// Implementation details only, past this point. +namespace strings_internal { + +struct ViableSubstitution { + absl::string_view old; + absl::string_view replacement; + size_t offset; + + ViableSubstitution(absl::string_view old_str, + absl::string_view replacement_str, size_t offset_val) + : old(old_str), replacement(replacement_str), offset(offset_val) {} + + // One substitution occurs "before" another (takes priority) if either + // it has the lowest offset, or it has the same offset but a larger size. + bool OccursBefore(const ViableSubstitution& y) const { + if (offset != y.offset) return offset < y.offset; + return old.size() > y.old.size(); + } +}; + +// Build a vector of ViableSubstitutions based on the given list of +// replacements. subs can be implemented as a priority_queue. However, it turns +// out that most callers have small enough a list of substitutions that the +// overhead of such a queue isn't worth it. +template +std::vector FindSubstitutions( + absl::string_view s, const StrToStrMapping& replacements) { + std::vector subs; + subs.reserve(replacements.size()); + + for (const auto& rep : replacements) { + using std::get; + absl::string_view old(get<0>(rep)); + + size_t pos = s.find(old); + if (pos == s.npos) continue; + + // Ignore attempts to replace "". This condition is almost never true, + // but above condition is frequently true. That's why we test for this + // now and not before. + if (old.empty()) continue; + + subs.emplace_back(old, get<1>(rep), pos); + + // Insertion sort to ensure the last ViableSubstitution comes before + // all the others. + size_t index = subs.size(); + while (--index && subs[index - 1].OccursBefore(subs[index])) { + std::swap(subs[index], subs[index - 1]); + } + } + return subs; +} + +int ApplySubstitutions(absl::string_view s, + std::vector* subs_ptr, + std::string* result_ptr); + +} // namespace strings_internal + +template +std::string StrReplaceAll(absl::string_view s, const StrToStrMapping& replacements) { + auto subs = strings_internal::FindSubstitutions(s, replacements); + std::string result; + result.reserve(s.size()); + strings_internal::ApplySubstitutions(s, &subs, &result); + return result; +} + +template +int StrReplaceAll(const StrToStrMapping& replacements, std::string* target) { + auto subs = strings_internal::FindSubstitutions(*target, replacements); + if (subs.empty()) return 0; + + std::string result; + result.reserve(target->size()); + int substitutions = + strings_internal::ApplySubstitutions(*target, &subs, &result); + target->swap(result); + return substitutions; +} + +} // namespace absl + +#endif // ABSL_STRINGS_STR_REPLACE_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_replace_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_replace_test.cc new file mode 100644 index 00000000000..5d003a224a4 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_replace_test.cc @@ -0,0 +1,341 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/str_replace.h" + +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" + +TEST(StrReplaceAll, OneReplacement) { + std::string s; + + // Empty std::string. + s = absl::StrReplaceAll(s, {{"", ""}}); + EXPECT_EQ(s, ""); + s = absl::StrReplaceAll(s, {{"x", ""}}); + EXPECT_EQ(s, ""); + s = absl::StrReplaceAll(s, {{"", "y"}}); + EXPECT_EQ(s, ""); + s = absl::StrReplaceAll(s, {{"x", "y"}}); + EXPECT_EQ(s, ""); + + // Empty substring. + s = absl::StrReplaceAll("abc", {{"", ""}}); + EXPECT_EQ(s, "abc"); + s = absl::StrReplaceAll("abc", {{"", "y"}}); + EXPECT_EQ(s, "abc"); + s = absl::StrReplaceAll("abc", {{"x", ""}}); + EXPECT_EQ(s, "abc"); + + // Substring not found. + s = absl::StrReplaceAll("abc", {{"xyz", "123"}}); + EXPECT_EQ(s, "abc"); + + // Replace entire std::string. + s = absl::StrReplaceAll("abc", {{"abc", "xyz"}}); + EXPECT_EQ(s, "xyz"); + + // Replace once at the start. + s = absl::StrReplaceAll("abc", {{"a", "x"}}); + EXPECT_EQ(s, "xbc"); + + // Replace once in the middle. + s = absl::StrReplaceAll("abc", {{"b", "x"}}); + EXPECT_EQ(s, "axc"); + + // Replace once at the end. + s = absl::StrReplaceAll("abc", {{"c", "x"}}); + EXPECT_EQ(s, "abx"); + + // Replace multiple times with varying lengths of original/replacement. + s = absl::StrReplaceAll("ababa", {{"a", "xxx"}}); + EXPECT_EQ(s, "xxxbxxxbxxx"); + + s = absl::StrReplaceAll("ababa", {{"b", "xxx"}}); + EXPECT_EQ(s, "axxxaxxxa"); + + s = absl::StrReplaceAll("aaabaaabaaa", {{"aaa", "x"}}); + EXPECT_EQ(s, "xbxbx"); + + s = absl::StrReplaceAll("abbbabbba", {{"bbb", "x"}}); + EXPECT_EQ(s, "axaxa"); + + // Overlapping matches are replaced greedily. + s = absl::StrReplaceAll("aaa", {{"aa", "x"}}); + EXPECT_EQ(s, "xa"); + + // The replacements are not recursive. + s = absl::StrReplaceAll("aaa", {{"aa", "a"}}); + EXPECT_EQ(s, "aa"); +} + +TEST(StrReplaceAll, ManyReplacements) { + std::string s; + + // Empty std::string. + s = absl::StrReplaceAll("", {{"", ""}, {"x", ""}, {"", "y"}, {"x", "y"}}); + EXPECT_EQ(s, ""); + + // Empty substring. + s = absl::StrReplaceAll("abc", {{"", ""}, {"", "y"}, {"x", ""}}); + EXPECT_EQ(s, "abc"); + + // Replace entire std::string, one char at a time + s = absl::StrReplaceAll("abc", {{"a", "x"}, {"b", "y"}, {"c", "z"}}); + EXPECT_EQ(s, "xyz"); + s = absl::StrReplaceAll("zxy", {{"z", "x"}, {"x", "y"}, {"y", "z"}}); + EXPECT_EQ(s, "xyz"); + + // Replace once at the start (longer matches take precedence) + s = absl::StrReplaceAll("abc", {{"a", "x"}, {"ab", "xy"}, {"abc", "xyz"}}); + EXPECT_EQ(s, "xyz"); + + // Replace once in the middle. + s = absl::StrReplaceAll( + "Abc!", {{"a", "x"}, {"ab", "xy"}, {"b", "y"}, {"bc", "yz"}, {"c", "z"}}); + EXPECT_EQ(s, "Ayz!"); + + // Replace once at the end. + s = absl::StrReplaceAll( + "Abc!", + {{"a", "x"}, {"ab", "xy"}, {"b", "y"}, {"bc!", "yz?"}, {"c!", "z;"}}); + EXPECT_EQ(s, "Ayz?"); + + // Replace multiple times with varying lengths of original/replacement. + s = absl::StrReplaceAll("ababa", {{"a", "xxx"}, {"b", "XXXX"}}); + EXPECT_EQ(s, "xxxXXXXxxxXXXXxxx"); + + // Overlapping matches are replaced greedily. + s = absl::StrReplaceAll("aaa", {{"aa", "x"}, {"a", "X"}}); + EXPECT_EQ(s, "xX"); + s = absl::StrReplaceAll("aaa", {{"a", "X"}, {"aa", "x"}}); + EXPECT_EQ(s, "xX"); + + // Two well-known sentences + s = absl::StrReplaceAll("the quick brown fox jumped over the lazy dogs", + { + {"brown", "box"}, + {"dogs", "jugs"}, + {"fox", "with"}, + {"jumped", "five"}, + {"over", "dozen"}, + {"quick", "my"}, + {"the", "pack"}, + {"the lazy", "liquor"}, + }); + EXPECT_EQ(s, "pack my box with five dozen liquor jugs"); +} + +TEST(StrReplaceAll, ManyReplacementsInMap) { + std::map replacements; + replacements["$who"] = "Bob"; + replacements["$count"] = "5"; + replacements["#Noun"] = "Apples"; + std::string s = absl::StrReplaceAll("$who bought $count #Noun. Thanks $who!", + replacements); + EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); +} + +TEST(StrReplaceAll, ReplacementsInPlace) { + std::string s = std::string("$who bought $count #Noun. Thanks $who!"); + int count; + count = absl::StrReplaceAll({{"$count", absl::StrCat(5)}, + {"$who", "Bob"}, + {"#Noun", "Apples"}}, &s); + EXPECT_EQ(count, 4); + EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); +} + +TEST(StrReplaceAll, ReplacementsInPlaceInMap) { + std::string s = std::string("$who bought $count #Noun. Thanks $who!"); + std::map replacements; + replacements["$who"] = "Bob"; + replacements["$count"] = "5"; + replacements["#Noun"] = "Apples"; + int count; + count = absl::StrReplaceAll(replacements, &s); + EXPECT_EQ(count, 4); + EXPECT_EQ("Bob bought 5 Apples. Thanks Bob!", s); +} + +struct Cont { + Cont() {} + explicit Cont(absl::string_view src) : data(src) {} + + absl::string_view data; +}; + +template +absl::string_view get(const Cont& c) { + auto splitter = absl::StrSplit(c.data, ':'); + auto it = splitter.begin(); + for (int i = 0; i < index; ++i) ++it; + + return *it; +} + +TEST(StrReplaceAll, VariableNumber) { + std::string s; + { + std::vector> replacements; + + s = "abc"; + EXPECT_EQ(0, absl::StrReplaceAll(replacements, &s)); + EXPECT_EQ("abc", s); + + s = "abc"; + replacements.push_back({"a", "A"}); + EXPECT_EQ(1, absl::StrReplaceAll(replacements, &s)); + EXPECT_EQ("Abc", s); + + s = "abc"; + replacements.push_back({"b", "B"}); + EXPECT_EQ(2, absl::StrReplaceAll(replacements, &s)); + EXPECT_EQ("ABc", s); + + s = "abc"; + replacements.push_back({"d", "D"}); + EXPECT_EQ(2, absl::StrReplaceAll(replacements, &s)); + EXPECT_EQ("ABc", s); + + EXPECT_EQ("ABcABc", absl::StrReplaceAll("abcabc", replacements)); + } + + { + std::map replacements; + replacements["aa"] = "x"; + replacements["a"] = "X"; + s = "aaa"; + EXPECT_EQ(2, absl::StrReplaceAll(replacements, &s)); + EXPECT_EQ("xX", s); + + EXPECT_EQ("xxX", absl::StrReplaceAll("aaaaa", replacements)); + } + + { + std::list> replacements = { + {"a", "x"}, {"b", "y"}, {"c", "z"}}; + + std::string s = absl::StrReplaceAll("abc", replacements); + EXPECT_EQ(s, "xyz"); + } + + { + using X = std::tuple; + std::vector replacements(3); + replacements[0] = X{"a", "x", 1}; + replacements[1] = X{"b", "y", 0}; + replacements[2] = X{"c", "z", -1}; + + std::string s = absl::StrReplaceAll("abc", replacements); + EXPECT_EQ(s, "xyz"); + } + + { + std::vector replacements(3); + replacements[0] = Cont{"a:x"}; + replacements[1] = Cont{"b:y"}; + replacements[2] = Cont{"c:z"}; + + std::string s = absl::StrReplaceAll("abc", replacements); + EXPECT_EQ(s, "xyz"); + } +} + +// Same as above, but using the in-place variant of absl::StrReplaceAll, +// that returns the # of replacements performed. +TEST(StrReplaceAll, Inplace) { + std::string s; + int reps; + + // Empty std::string. + s = ""; + reps = absl::StrReplaceAll({{"", ""}, {"x", ""}, {"", "y"}, {"x", "y"}}, &s); + EXPECT_EQ(reps, 0); + EXPECT_EQ(s, ""); + + // Empty substring. + s = "abc"; + reps = absl::StrReplaceAll({{"", ""}, {"", "y"}, {"x", ""}}, &s); + EXPECT_EQ(reps, 0); + EXPECT_EQ(s, "abc"); + + // Replace entire std::string, one char at a time + s = "abc"; + reps = absl::StrReplaceAll({{"a", "x"}, {"b", "y"}, {"c", "z"}}, &s); + EXPECT_EQ(reps, 3); + EXPECT_EQ(s, "xyz"); + s = "zxy"; + reps = absl::StrReplaceAll({{"z", "x"}, {"x", "y"}, {"y", "z"}}, &s); + EXPECT_EQ(reps, 3); + EXPECT_EQ(s, "xyz"); + + // Replace once at the start (longer matches take precedence) + s = "abc"; + reps = absl::StrReplaceAll({{"a", "x"}, {"ab", "xy"}, {"abc", "xyz"}}, &s); + EXPECT_EQ(reps, 1); + EXPECT_EQ(s, "xyz"); + + // Replace once in the middle. + s = "Abc!"; + reps = absl::StrReplaceAll( + {{"a", "x"}, {"ab", "xy"}, {"b", "y"}, {"bc", "yz"}, {"c", "z"}}, &s); + EXPECT_EQ(reps, 1); + EXPECT_EQ(s, "Ayz!"); + + // Replace once at the end. + s = "Abc!"; + reps = absl::StrReplaceAll( + {{"a", "x"}, {"ab", "xy"}, {"b", "y"}, {"bc!", "yz?"}, {"c!", "z;"}}, &s); + EXPECT_EQ(reps, 1); + EXPECT_EQ(s, "Ayz?"); + + // Replace multiple times with varying lengths of original/replacement. + s = "ababa"; + reps = absl::StrReplaceAll({{"a", "xxx"}, {"b", "XXXX"}}, &s); + EXPECT_EQ(reps, 5); + EXPECT_EQ(s, "xxxXXXXxxxXXXXxxx"); + + // Overlapping matches are replaced greedily. + s = "aaa"; + reps = absl::StrReplaceAll({{"aa", "x"}, {"a", "X"}}, &s); + EXPECT_EQ(reps, 2); + EXPECT_EQ(s, "xX"); + s = "aaa"; + reps = absl::StrReplaceAll({{"a", "X"}, {"aa", "x"}}, &s); + EXPECT_EQ(reps, 2); + EXPECT_EQ(s, "xX"); + + // Two well-known sentences + s = "the quick brown fox jumped over the lazy dogs"; + reps = absl::StrReplaceAll( + { + {"brown", "box"}, + {"dogs", "jugs"}, + {"fox", "with"}, + {"jumped", "five"}, + {"over", "dozen"}, + {"quick", "my"}, + {"the", "pack"}, + {"the lazy", "liquor"}, + }, + &s); + EXPECT_EQ(reps, 8); + EXPECT_EQ(s, "pack my box with five dozen liquor jugs"); +} diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_split.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_split.cc new file mode 100644 index 00000000000..0207213c26b --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_split.cc @@ -0,0 +1,136 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/str_split.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/ascii.h" + +namespace absl { + +namespace { + +// This GenericFind() template function encapsulates the finding algorithm +// shared between the ByString and ByAnyChar delimiters. The FindPolicy +// template parameter allows each delimiter to customize the actual find +// function to use and the length of the found delimiter. For example, the +// Literal delimiter will ultimately use absl::string_view::find(), and the +// AnyOf delimiter will use absl::string_view::find_first_of(). +template +absl::string_view GenericFind(absl::string_view text, + absl::string_view delimiter, size_t pos, + FindPolicy find_policy) { + if (delimiter.empty() && text.length() > 0) { + // Special case for empty std::string delimiters: always return a zero-length + // absl::string_view referring to the item at position 1 past pos. + return absl::string_view(text.begin() + pos + 1, 0); + } + size_t found_pos = absl::string_view::npos; + absl::string_view found(text.end(), 0); // By default, not found + found_pos = find_policy.Find(text, delimiter, pos); + if (found_pos != absl::string_view::npos) { + found = absl::string_view(text.data() + found_pos, + find_policy.Length(delimiter)); + } + return found; +} + +// Finds using absl::string_view::find(), therefore the length of the found +// delimiter is delimiter.length(). +struct LiteralPolicy { + size_t Find(absl::string_view text, absl::string_view delimiter, size_t pos) { + return text.find(delimiter, pos); + } + size_t Length(absl::string_view delimiter) { return delimiter.length(); } +}; + +// Finds using absl::string_view::find_first_of(), therefore the length of the +// found delimiter is 1. +struct AnyOfPolicy { + size_t Find(absl::string_view text, absl::string_view delimiter, size_t pos) { + return text.find_first_of(delimiter, pos); + } + size_t Length(absl::string_view /* delimiter */) { return 1; } +}; + +} // namespace + +// +// ByString +// + +ByString::ByString(absl::string_view sp) : delimiter_(sp) {} + +absl::string_view ByString::Find(absl::string_view text, size_t pos) const { + if (delimiter_.length() == 1) { + // Much faster to call find on a single character than on an + // absl::string_view. + size_t found_pos = text.find(delimiter_[0], pos); + if (found_pos == absl::string_view::npos) + return absl::string_view(text.end(), 0); + return text.substr(found_pos, 1); + } + return GenericFind(text, delimiter_, pos, LiteralPolicy()); +} + +// +// ByChar +// + +absl::string_view ByChar::Find(absl::string_view text, size_t pos) const { + size_t found_pos = text.find(c_, pos); + if (found_pos == absl::string_view::npos) + return absl::string_view(text.end(), 0); + return text.substr(found_pos, 1); +} + +// +// ByAnyChar +// + +ByAnyChar::ByAnyChar(absl::string_view sp) : delimiters_(sp) {} + +absl::string_view ByAnyChar::Find(absl::string_view text, size_t pos) const { + return GenericFind(text, delimiters_, pos, AnyOfPolicy()); +} + +// +// ByLength +// +ByLength::ByLength(ptrdiff_t length) : length_(length) { + ABSL_RAW_CHECK(length > 0, ""); +} + +absl::string_view ByLength::Find(absl::string_view text, + size_t pos) const { + pos = std::min(pos, text.size()); // truncate `pos` + absl::string_view substr = text.substr(pos); + // If the std::string is shorter than the chunk size we say we + // "can't find the delimiter" so this will be the last chunk. + if (substr.length() <= static_cast(length_)) + return absl::string_view(text.end(), 0); + + return absl::string_view(substr.begin() + length_, 0); +} + +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_split.h b/Firestore/third_party/abseil-cpp/absl/strings/str_split.h new file mode 100644 index 00000000000..5b3d6a8a91e --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_split.h @@ -0,0 +1,511 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: str_split.h +// ----------------------------------------------------------------------------- +// +// This file contains functions for splitting strings. It defines the main +// `StrSplit()` function, several delimiters for determining the boundaries on +// which to split the std::string, and predicates for filtering delimited results. +// `StrSplit()` adapts the returned collection to the type specified by the +// caller. +// +// Example: +// +// // Splits the given std::string on commas. Returns the results in a +// // vector of strings. +// std::vector v = absl::StrSplit("a,b,c", ','); +// // Can also use "," +// // v[0] == "a", v[1] == "b", v[2] == "c" +// +// See StrSplit() below for more information. +#ifndef ABSL_STRINGS_STR_SPLIT_H_ +#define ABSL_STRINGS_STR_SPLIT_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/internal/str_split_internal.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" + +namespace absl { + +//------------------------------------------------------------------------------ +// Delimiters +//------------------------------------------------------------------------------ +// +// `StrSplit()` uses delimiters to define the boundaries between elements in the +// provided input. Several `Delimiter` types are defined below. If a std::string +// (`const char*`, `std::string`, or `absl::string_view`) is passed in place of +// an explicit `Delimiter` object, `StrSplit()` treats it the same way as if it +// were passed a `ByString` delimiter. +// +// A `Delimiter` is an object with a `Find()` function that knows how to find +// the first occurrence of itself in a given `absl::string_view`. +// +// The following `Delimiter` types are available for use within `StrSplit()`: +// +// - `ByString` (default for std::string arguments) +// - `ByChar` (default for a char argument) +// - `ByAnyChar` +// - `ByLength` +// - `MaxSplits` +// +// +// A Delimiter's Find() member function will be passed the input text that is to +// be split and the position to begin searching for the next delimiter in the +// input text. The returned absl::string_view should refer to the next +// occurrence (after pos) of the represented delimiter; this returned +// absl::string_view represents the next location where the input std::string should +// be broken. The returned absl::string_view may be zero-length if the Delimiter +// does not represent a part of the std::string (e.g., a fixed-length delimiter). If +// no delimiter is found in the given text, a zero-length absl::string_view +// referring to text.end() should be returned (e.g., +// absl::string_view(text.end(), 0)). It is important that the returned +// absl::string_view always be within the bounds of input text given as an +// argument--it must not refer to a std::string that is physically located outside of +// the given std::string. +// +// The following example is a simple Delimiter object that is created with a +// single char and will look for that char in the text passed to the Find() +// function: +// +// struct SimpleDelimiter { +// const char c_; +// explicit SimpleDelimiter(char c) : c_(c) {} +// absl::string_view Find(absl::string_view text, size_t pos) { +// auto found = text.find(c_, pos); +// if (found == absl::string_view::npos) +// return absl::string_view(text.end(), 0); +// +// return absl::string_view(text, found, 1); +// } +// }; + +// ByString +// +// A sub-std::string delimiter. If `StrSplit()` is passed a std::string in place of a +// `Delimiter` object, the std::string will be implicitly converted into a +// `ByString` delimiter. +// +// Example: +// +// // Because a std::string literal is converted to an `absl::ByString`, +// // the following two splits are equivalent. +// +// std::vector v1 = absl::StrSplit("a, b, c", ", "); +// +// using absl::ByString; +// std::vector v2 = absl::StrSplit("a, b, c", +// ByString(", ")); +// // v[0] == "a", v[1] == "b", v[2] == "c" +class ByString { + public: + explicit ByString(absl::string_view sp); + absl::string_view Find(absl::string_view text, size_t pos) const; + + private: + const std::string delimiter_; +}; + +// ByChar +// +// A single character delimiter. `ByChar` is functionally equivalent to a +// 1-char std::string within a `ByString` delimiter, but slightly more +// efficient. +// +// Example: +// +// // Because a char literal is converted to a absl::ByChar, +// // the following two splits are equivalent. +// std::vector v1 = absl::StrSplit("a,b,c", ','); +// using absl::ByChar; +// std::vector v2 = absl::StrSplit("a,b,c", ByChar(',')); +// // v[0] == "a", v[1] == "b", v[2] == "c" +// +// `ByChar` is also the default delimiter if a single character is given +// as the delimiter to `StrSplit()`. For example, the following calls are +// equivalent: +// +// std::vector v = absl::StrSplit("a-b", '-'); +// +// using absl::ByChar; +// std::vector v = absl::StrSplit("a-b", ByChar('-')); +// +class ByChar { + public: + explicit ByChar(char c) : c_(c) {} + absl::string_view Find(absl::string_view text, size_t pos) const; + + private: + char c_; +}; + +// ByAnyChar +// +// A delimiter that will match any of the given byte-sized characters within +// its provided std::string. +// +// Note: this delimiter works with single-byte std::string data, but does not work +// with variable-width encodings, such as UTF-8. +// +// Example: +// +// using absl::ByAnyChar; +// std::vector v = absl::StrSplit("a,b=c", ByAnyChar(",=")); +// // v[0] == "a", v[1] == "b", v[2] == "c" +// +// If `ByAnyChar` is given the empty std::string, it behaves exactly like +// `ByString` and matches each individual character in the input std::string. +// +class ByAnyChar { + public: + explicit ByAnyChar(absl::string_view sp); + absl::string_view Find(absl::string_view text, size_t pos) const; + + private: + const std::string delimiters_; +}; + +// ByLength +// +// A delimiter for splitting into equal-length strings. The length argument to +// the constructor must be greater than 0. +// +// Note: this delimiter works with single-byte std::string data, but does not work +// with variable-width encodings, such as UTF-8. +// +// Example: +// +// using absl::ByLength; +// std::vector v = absl::StrSplit("123456789", ByLength(3)); + +// // v[0] == "123", v[1] == "456", v[2] == "789" +// +// Note that the std::string does not have to be a multiple of the fixed split +// length. In such a case, the last substring will be shorter. +// +// using absl::ByLength; +// std::vector v = absl::StrSplit("12345", ByLength(2)); +// +// // v[0] == "12", v[1] == "35", v[2] == "5" +class ByLength { + public: + explicit ByLength(ptrdiff_t length); + absl::string_view Find(absl::string_view text, size_t pos) const; + + private: + const ptrdiff_t length_; +}; + +namespace strings_internal { + +// A traits-like metafunction for selecting the default Delimiter object type +// for a particular Delimiter type. The base case simply exposes type Delimiter +// itself as the delimiter's Type. However, there are specializations for +// std::string-like objects that map them to the ByString delimiter object. +// This allows functions like absl::StrSplit() and absl::MaxSplits() to accept +// std::string-like objects (e.g., ',') as delimiter arguments but they will be +// treated as if a ByString delimiter was given. +template +struct SelectDelimiter { + using type = Delimiter; +}; + +template <> +struct SelectDelimiter { + using type = ByChar; +}; +template <> +struct SelectDelimiter { + using type = ByString; +}; +template <> +struct SelectDelimiter { + using type = ByString; +}; +template <> +struct SelectDelimiter { + using type = ByString; +}; +template <> +struct SelectDelimiter { + using type = ByString; +}; + +// Wraps another delimiter and sets a max number of matches for that delimiter. +template +class MaxSplitsImpl { + public: + MaxSplitsImpl(Delimiter delimiter, int limit) + : delimiter_(delimiter), limit_(limit), count_(0) {} + absl::string_view Find(absl::string_view text, size_t pos) { + if (count_++ == limit_) { + return absl::string_view(text.end(), 0); // No more matches. + } + return delimiter_.Find(text, pos); + } + + private: + Delimiter delimiter_; + const int limit_; + int count_; +}; + +} // namespace strings_internal + +// MaxSplits() +// +// A delimiter that limits the number of matches which can occur to the passed +// `limit`. The last element in the returned collection will contain all +// remaining unsplit pieces, which may contain instances of the delimiter. +// The collection will contain at most `limit` + 1 elements. +// Example: +// +// using absl::MaxSplits; +// std::vector v = absl::StrSplit("a,b,c", MaxSplits(',', 1)); +// +// // v[0] == "a", v[1] == "b,c" +template +inline strings_internal::MaxSplitsImpl< + typename strings_internal::SelectDelimiter::type> +MaxSplits(Delimiter delimiter, int limit) { + typedef + typename strings_internal::SelectDelimiter::type DelimiterType; + return strings_internal::MaxSplitsImpl( + DelimiterType(delimiter), limit); +} + +//------------------------------------------------------------------------------ +// Predicates +//------------------------------------------------------------------------------ +// +// Predicates filter the results of a `StrSplit()` by determining whether or not +// a resultant element is included in the result set. A predicate may be passed +// as an optional third argument to the `StrSplit()` function. +// +// Predicates are unary functions (or functors) that take a single +// `absl::string_view` argument and return a bool indicating whether the +// argument should be included (`true`) or excluded (`false`). +// +// Predicates are useful when filtering out empty substrings. By default, empty +// substrings may be returned by `StrSplit()`, which is similar to the way split +// functions work in other programming languages. + +// AllowEmpty() +// +// Always returns `true`, indicating that all strings--including empty +// strings--should be included in the split output. This predicate is not +// strictly needed because this is the default behavior of `StrSplit()`; +// however, it might be useful at some call sites to make the intent explicit. +// +// Example: +// +// std::vector v = absl::StrSplit(" a , ,,b,", ',', AllowEmpty()); +// +// // v[0] == " a ", v[1] == " ", v[2] == "", v[3] = "b", v[4] == "" +struct AllowEmpty { + bool operator()(absl::string_view) const { return true; } +}; + +// SkipEmpty() +// +// Returns `false` if the given `absl::string_view` is empty, indicating that +// `StrSplit()` should omit the empty std::string. +// +// Example: +// +// std::vector v = absl::StrSplit(",a,,b,", ',', SkipEmpty()); +// +// // v[0] == "a", v[1] == "b" +// +// Note: `SkipEmpty()` does not consider a std::string containing only whitespace +// to be empty. To skip such whitespace as well, use the `SkipWhitespace()` +// predicate. +struct SkipEmpty { + bool operator()(absl::string_view sp) const { return !sp.empty(); } +}; + +// SkipWhitespace() +// +// Returns `false` if the given `absl::string_view` is empty *or* contains only +// whitespace, indicating that `StrSplit()` should omit the std::string. +// +// Example: +// +// std::vector v = absl::StrSplit(" a , ,,b,", +// ',', SkipWhitespace()); +// // v[0] == " a ", v[1] == "b" +// +// // SkipEmpty() would return whitespace elements +// std::vector v = absl::StrSplit(" a , ,,b,", ',', SkipEmpty()); +// // v[0] == " a ", v[1] == " ", v[2] == "b" +struct SkipWhitespace { + bool operator()(absl::string_view sp) const { + sp = absl::StripAsciiWhitespace(sp); + return !sp.empty(); + } +}; + +//------------------------------------------------------------------------------ +// StrSplit() +//------------------------------------------------------------------------------ + +// StrSplit() +// +// Splits a given `std::string` based on the provided `Delimiter` object, +// returning the elements within the type specified by the caller. Optionally, +// you may also pass a `Predicate` to `StrSplit()` indicating whether to include +// or exclude the resulting element within the final result set. (See the +// overviews for Delimiters and Predicates above.) +// +// Example: +// +// std::vector v = absl::StrSplit("a,b,c,d", ','); +// // v[0] == "a", v[1] == "b", v[2] == "c", v[3] == "d" +// +// You can also provide an explicit `Delimiter` object: +// +// Example: +// +// using absl::ByAnyChar; +// std::vector v = absl::StrSplit("a,b=c", ByAnyChar(",=")); +// // v[0] == "a", v[1] == "b", v[2] == "c" +// +// See above for more information on delimiters. +// +// By default, empty strings are included in the result set. You can optionally +// include a third `Predicate` argument to apply a test for whether the +// resultant element should be included in the result set: +// +// Example: +// +// std::vector v = absl::StrSplit(" a , ,,b,", +// ',', SkipWhitespace()); +// // v[0] == "a", v[1] == "b" +// +// See above for more information on predicates. +// +//------------------------------------------------------------------------------ +// StrSplit() Return Types +//------------------------------------------------------------------------------ +// +// The `StrSplit()` function adapts the returned collection to the collection +// specified by the caller (e.g. `std::vector` above). The returned collections +// may contain `string`, `absl::string_view` (in which case the original std::string +// being split must ensure that it outlives the collection), or any object that +// can be explicitly created from an `absl::string_view`. This behavior works +// for: +// +// 1) All standard STL containers including `std::vector`, `std::list`, +// `std::deque`, `std::set`,`std::multiset`, 'std::map`, and `std::multimap` +// 2) `std::pair` (which is not actually a container). See below. +// +// Example: +// +// // The results are returned as `absl::string_view` objects. Note that we +// // have to ensure that the input std::string outlives any results. +// std::vector v = absl::StrSplit("a,b,c", ','); +// +// // Stores results in a std::set, which also performs +// // de-duplication and orders the elements in ascending order. +// std::set a = absl::StrSplit("b,a,c,a,b", ','); +// // v[0] == "a", v[1] == "b", v[2] = "c" +// +// // `StrSplit()` can be used within a range-based for loop, in which case +// // each element will be of type `absl::string_view`. +// std::vector v; +// for (const auto sv : absl::StrSplit("a,b,c", ',')) { +// if (sv != "b") v.emplace_back(sv); +// } +// // v[0] == "a", v[1] == "c" +// +// // Stores results in a map. The map implementation assumes that the input +// // is provided as a series of key/value pairs. For example, the 0th element +// // resulting from the split will be stored as a key to the 1st element. If +// // an odd number of elements are resolved, the last element is paired with +// // a default-constructed value (e.g., empty std::string). +// std::map m = absl::StrSplit("a,b,c", ','); +// // m["a"] == "b", m["c"] == "" // last component value equals "" +// +// Splitting to `std::pair` is an interesting case because it can hold only two +// elements and is not a collection type. When splitting to a `std::pair` the +// first two split strings become the `std::pair` `.first` and `.second` +// members, respectively. The remaining split substrings are discarded. If there +// are less than two split substrings, the empty std::string is used for the +// corresponding +// `std::pair` member. +// +// Example: +// +// // Stores first two split strings as the members in a std::pair. +// std::pair p = absl::StrSplit("a,b,c", ','); +// // p.first == "a", p.second == "b" // "c" is omitted. +// +// The `StrSplit()` function can be used multiple times to perform more +// complicated splitting logic, such as intelligently parsing key-value pairs. +// +// Example: +// +// // The input std::string "a=b=c,d=e,f=,g" becomes +// // { "a" => "b=c", "d" => "e", "f" => "", "g" => "" } +// std::map m; +// for (absl::string_view sp : absl::StrSplit("a=b=c,d=e,f=,g", ',')) { +// m.insert(absl::StrSplit(sp, absl::MaxSplits('=', 1))); +// } +// EXPECT_EQ("b=c", m.find("a")->second); +// EXPECT_EQ("e", m.find("d")->second); +// EXPECT_EQ("", m.find("f")->second); +// EXPECT_EQ("", m.find("g")->second); +// +// WARNING: Due to a legacy bug that is maintained for backward compatibility, +// splitting the following empty string_views produces different results: +// +// absl::StrSplit(absl::string_view(""), '-'); // {""} +// absl::StrSplit(absl::string_view(), '-'); // {}, but should be {""} +// +// Try not to depend on this distinction because the bug may one day be fixed. +template +strings_internal::Splitter< + typename strings_internal::SelectDelimiter::type, AllowEmpty> +StrSplit(strings_internal::ConvertibleToStringView text, Delimiter d) { + using DelimiterType = + typename strings_internal::SelectDelimiter::type; + return strings_internal::Splitter( + std::move(text), DelimiterType(d), AllowEmpty()); +} + +template +strings_internal::Splitter< + typename strings_internal::SelectDelimiter::type, Predicate> +StrSplit(strings_internal::ConvertibleToStringView text, Delimiter d, + Predicate p) { + using DelimiterType = + typename strings_internal::SelectDelimiter::type; + return strings_internal::Splitter( + std::move(text), DelimiterType(d), std::move(p)); +} + +} // namespace absl + +#endif // ABSL_STRINGS_STR_SPLIT_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_split_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_split_test.cc new file mode 100644 index 00000000000..b1db1c5647e --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_split_test.cc @@ -0,0 +1,907 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/str_split.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/dynamic_annotations.h" // for RunningOnValgrind +#include "absl/base/macros.h" +#include "absl/strings/numbers.h" + +namespace { + +using ::testing::ElementsAre; +using ::testing::Pair; +using ::testing::UnorderedElementsAre; + +// This tests the overall split API, which is made up of the absl::StrSplit() +// function and the Delimiter objects in the absl:: namespace. +// This TEST macro is outside of any namespace to require full specification of +// namespaces just like callers will need to use. +TEST(Split, APIExamples) { + { + // Passes std::string delimiter. Assumes the default of Literal. + std::vector v = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + + // Equivalent to... + using absl::ByString; + v = absl::StrSplit("a,b,c", ByString(",")); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + + // Equivalent to... + EXPECT_THAT(absl::StrSplit("a,b,c", ByString(",")), + ElementsAre("a", "b", "c")); + } + + { + // Same as above, but using a single character as the delimiter. + std::vector v = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + + // Equivalent to... + using absl::ByChar; + v = absl::StrSplit("a,b,c", ByChar(',')); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Same as above, but using std::string + std::vector v = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + + // Equivalent to... + using absl::ByChar; + v = absl::StrSplit("a,b,c", ByChar(',')); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Uses the Literal std::string "=>" as the delimiter. + const std::vector v = absl::StrSplit("a=>b=>c", "=>"); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // The substrings are returned as string_views, eliminating copying. + std::vector v = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Leading and trailing empty substrings. + std::vector v = absl::StrSplit(",a,b,c,", ','); + EXPECT_THAT(v, ElementsAre("", "a", "b", "c", "")); + } + + { + // Splits on a delimiter that is not found. + std::vector v = absl::StrSplit("abc", ','); + EXPECT_THAT(v, ElementsAre("abc")); + } + + { + // Splits the input std::string into individual characters by using an empty + // std::string as the delimiter. + std::vector v = absl::StrSplit("abc", ""); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Splits std::string data with embedded NUL characters, using NUL as the + // delimiter. A simple delimiter of "\0" doesn't work because strlen() will + // say that's the empty std::string when constructing the absl::string_view + // delimiter. Instead, a non-empty std::string containing NUL can be used as the + // delimiter. + std::string embedded_nulls("a\0b\0c", 5); + std::string null_delim("\0", 1); + std::vector v = absl::StrSplit(embedded_nulls, null_delim); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Stores first two split strings as the members in a std::pair. + std::pair p = absl::StrSplit("a,b,c", ','); + EXPECT_EQ("a", p.first); + EXPECT_EQ("b", p.second); + // "c" is omitted because std::pair can hold only two elements. + } + + { + // Results stored in std::set + std::set v = absl::StrSplit("a,b,c,a,b,c,a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Uses a non-const char* delimiter. + char a[] = ","; + char* d = a + 0; + std::vector v = absl::StrSplit("a,b,c", d); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Results split using either of , or ; + using absl::ByAnyChar; + std::vector v = absl::StrSplit("a,b;c", ByAnyChar(",;")); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Uses the SkipWhitespace predicate. + using absl::SkipWhitespace; + std::vector v = absl::StrSplit("a, ,,b,", ',', SkipWhitespace()); + EXPECT_THAT(v, ElementsAre("a", "b")); + } + + { + // Uses the ByLength delimiter. + using absl::ByLength; + std::vector v = absl::StrSplit("abcdefg", ByLength(3)); + EXPECT_THAT(v, ElementsAre("abc", "def", "g")); + } + + { + // Different forms of initialization / conversion. + std::vector v1 = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v1, ElementsAre("a", "b", "c")); + std::vector v2(absl::StrSplit("a,b,c", ',')); + EXPECT_THAT(v2, ElementsAre("a", "b", "c")); + auto v3 = std::vector(absl::StrSplit("a,b,c", ',')); + EXPECT_THAT(v3, ElementsAre("a", "b", "c")); + v3 = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v3, ElementsAre("a", "b", "c")); + } + + { + // Results stored in a std::map. + std::map m = absl::StrSplit("a,1,b,2,a,3", ','); + EXPECT_EQ(2, m.size()); + EXPECT_EQ("3", m["a"]); + EXPECT_EQ("2", m["b"]); + } + + { + // Results stored in a std::multimap. + std::multimap m = absl::StrSplit("a,1,b,2,a,3", ','); + EXPECT_EQ(3, m.size()); + auto it = m.find("a"); + EXPECT_EQ("1", it->second); + ++it; + EXPECT_EQ("3", it->second); + it = m.find("b"); + EXPECT_EQ("2", it->second); + } + + { + // Demonstrates use in a range-based for loop in C++11. + std::string s = "x,x,x,x,x,x,x"; + for (absl::string_view sp : absl::StrSplit(s, ',')) { + EXPECT_EQ("x", sp); + } + } + + { + // Demonstrates use with a Predicate in a range-based for loop. + using absl::SkipWhitespace; + std::string s = " ,x,,x,,x,x,x,,"; + for (absl::string_view sp : absl::StrSplit(s, ',', SkipWhitespace())) { + EXPECT_EQ("x", sp); + } + } + + { + // Demonstrates a "smart" split to std::map using two separate calls to + // absl::StrSplit. One call to split the records, and another call to split + // the keys and values. This also uses the Limit delimiter so that the + // std::string "a=b=c" will split to "a" -> "b=c". + std::map m; + for (absl::string_view sp : absl::StrSplit("a=b=c,d=e,f=,g", ',')) { + m.insert(absl::StrSplit(sp, absl::MaxSplits('=', 1))); + } + EXPECT_EQ("b=c", m.find("a")->second); + EXPECT_EQ("e", m.find("d")->second); + EXPECT_EQ("", m.find("f")->second); + EXPECT_EQ("", m.find("g")->second); + } +} + +// +// Tests for SplitIterator +// + +TEST(SplitIterator, Basics) { + auto splitter = absl::StrSplit("a,b", ','); + auto it = splitter.begin(); + auto end = splitter.end(); + + EXPECT_NE(it, end); + EXPECT_EQ("a", *it); // tests dereference + ++it; // tests preincrement + EXPECT_NE(it, end); + EXPECT_EQ("b", std::string(it->data(), it->size())); // tests dereference as ptr + it++; // tests postincrement + EXPECT_EQ(it, end); +} + +// Simple Predicate to skip a particular std::string. +class Skip { + public: + explicit Skip(const std::string& s) : s_(s) {} + bool operator()(absl::string_view sp) { return sp != s_; } + + private: + std::string s_; +}; + +TEST(SplitIterator, Predicate) { + auto splitter = absl::StrSplit("a,b,c", ',', Skip("b")); + auto it = splitter.begin(); + auto end = splitter.end(); + + EXPECT_NE(it, end); + EXPECT_EQ("a", *it); // tests dereference + ++it; // tests preincrement -- "b" should be skipped here. + EXPECT_NE(it, end); + EXPECT_EQ("c", std::string(it->data(), it->size())); // tests dereference as ptr + it++; // tests postincrement + EXPECT_EQ(it, end); +} + +TEST(SplitIterator, EdgeCases) { + // Expected input and output, assuming a delimiter of ',' + struct { + std::string in; + std::vector expect; + } specs[] = { + {"", {""}}, + {"foo", {"foo"}}, + {",", {"", ""}}, + {",foo", {"", "foo"}}, + {"foo,", {"foo", ""}}, + {",foo,", {"", "foo", ""}}, + {"foo,bar", {"foo", "bar"}}, + }; + + for (const auto& spec : specs) { + SCOPED_TRACE(spec.in); + auto splitter = absl::StrSplit(spec.in, ','); + auto it = splitter.begin(); + auto end = splitter.end(); + for (const auto& expected : spec.expect) { + EXPECT_NE(it, end); + EXPECT_EQ(expected, *it++); + } + EXPECT_EQ(it, end); + } +} + +TEST(Splitter, Const) { + const auto splitter = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(splitter, ElementsAre("a", "b", "c")); +} + +TEST(Split, EmptyAndNull) { + // Attention: Splitting a null absl::string_view is different than splitting + // an empty absl::string_view even though both string_views are considered + // equal. This behavior is likely surprising and undesirable. However, to + // maintain backward compatibility, there is a small "hack" in + // str_split_internal.h that preserves this behavior. If that behavior is ever + // changed/fixed, this test will need to be updated. + EXPECT_THAT(absl::StrSplit(absl::string_view(""), '-'), ElementsAre("")); + EXPECT_THAT(absl::StrSplit(absl::string_view(), '-'), ElementsAre()); +} + +TEST(SplitIterator, EqualityAsEndCondition) { + auto splitter = absl::StrSplit("a,b,c", ','); + auto it = splitter.begin(); + auto it2 = it; + + // Increments it2 twice to point to "c" in the input text. + ++it2; + ++it2; + EXPECT_EQ("c", *it2); + + // This test uses a non-end SplitIterator as the terminating condition in a + // for loop. This relies on SplitIterator equality for non-end SplitIterators + // working correctly. At this point it2 points to "c", and we use that as the + // "end" condition in this test. + std::vector v; + for (; it != it2; ++it) { + v.push_back(*it); + } + EXPECT_THAT(v, ElementsAre("a", "b")); +} + +// +// Tests for Splitter +// + +TEST(Splitter, RangeIterators) { + auto splitter = absl::StrSplit("a,b,c", ','); + std::vector output; + for (const absl::string_view p : splitter) { + output.push_back(p); + } + EXPECT_THAT(output, ElementsAre("a", "b", "c")); +} + +// Some template functions for use in testing conversion operators +template +void TestConversionOperator(const Splitter& splitter) { + ContainerType output = splitter; + EXPECT_THAT(output, UnorderedElementsAre("a", "b", "c", "d")); +} + +template +void TestMapConversionOperator(const Splitter& splitter) { + MapType m = splitter; + EXPECT_THAT(m, UnorderedElementsAre(Pair("a", "b"), Pair("c", "d"))); +} + +template +void TestPairConversionOperator(const Splitter& splitter) { + std::pair p = splitter; + EXPECT_EQ(p, (std::pair("a", "b"))); +} + +TEST(Splitter, ConversionOperator) { + auto splitter = absl::StrSplit("a,b,c,d", ','); + + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + TestConversionOperator>(splitter); + + // Tests conversion to map-like objects. + + TestMapConversionOperator>( + splitter); + TestMapConversionOperator>(splitter); + TestMapConversionOperator>(splitter); + TestMapConversionOperator>(splitter); + TestMapConversionOperator< + std::multimap>(splitter); + TestMapConversionOperator>(splitter); + TestMapConversionOperator>(splitter); + TestMapConversionOperator>(splitter); + TestMapConversionOperator>(splitter); + + // Tests conversion to std::pair + + TestPairConversionOperator(splitter); + TestPairConversionOperator(splitter); + TestPairConversionOperator(splitter); + TestPairConversionOperator(splitter); +} + +// A few additional tests for conversion to std::pair. This conversion is +// different from others because a std::pair always has exactly two elements: +// .first and .second. The split has to work even when the split has +// less-than, equal-to, and more-than 2 strings. +TEST(Splitter, ToPair) { + { + // Empty std::string + std::pair p = absl::StrSplit("", ','); + EXPECT_EQ("", p.first); + EXPECT_EQ("", p.second); + } + + { + // Only first + std::pair p = absl::StrSplit("a", ','); + EXPECT_EQ("a", p.first); + EXPECT_EQ("", p.second); + } + + { + // Only second + std::pair p = absl::StrSplit(",b", ','); + EXPECT_EQ("", p.first); + EXPECT_EQ("b", p.second); + } + + { + // First and second. + std::pair p = absl::StrSplit("a,b", ','); + EXPECT_EQ("a", p.first); + EXPECT_EQ("b", p.second); + } + + { + // First and second and then more stuff that will be ignored. + std::pair p = absl::StrSplit("a,b,c", ','); + EXPECT_EQ("a", p.first); + EXPECT_EQ("b", p.second); + // "c" is omitted. + } +} + +TEST(Splitter, Predicates) { + static const char kTestChars[] = ",a, ,b,"; + using absl::AllowEmpty; + using absl::SkipEmpty; + using absl::SkipWhitespace; + + { + // No predicate. Does not skip empties. + auto splitter = absl::StrSplit(kTestChars, ','); + std::vector v = splitter; + EXPECT_THAT(v, ElementsAre("", "a", " ", "b", "")); + } + + { + // Allows empty strings. Same behavior as no predicate at all. + auto splitter = absl::StrSplit(kTestChars, ',', AllowEmpty()); + std::vector v_allowempty = splitter; + EXPECT_THAT(v_allowempty, ElementsAre("", "a", " ", "b", "")); + + // Ensures AllowEmpty equals the behavior with no predicate. + auto splitter_nopredicate = absl::StrSplit(kTestChars, ','); + std::vector v_nopredicate = splitter_nopredicate; + EXPECT_EQ(v_allowempty, v_nopredicate); + } + + { + // Skips empty strings. + auto splitter = absl::StrSplit(kTestChars, ',', SkipEmpty()); + std::vector v = splitter; + EXPECT_THAT(v, ElementsAre("a", " ", "b")); + } + + { + // Skips empty and all-whitespace strings. + auto splitter = absl::StrSplit(kTestChars, ',', SkipWhitespace()); + std::vector v = splitter; + EXPECT_THAT(v, ElementsAre("a", "b")); + } +} + +// +// Tests for StrSplit() +// + +TEST(Split, Basics) { + { + // Doesn't really do anything useful because the return value is ignored, + // but it should work. + absl::StrSplit("a,b,c", ','); + } + + { + std::vector v = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + std::vector v = absl::StrSplit("a,b,c", ','); + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + } + + { + // Ensures that assignment works. This requires a little extra work with + // C++11 because of overloads with initializer_list. + std::vector v; + v = absl::StrSplit("a,b,c", ','); + + EXPECT_THAT(v, ElementsAre("a", "b", "c")); + std::map m; + m = absl::StrSplit("a,b,c", ','); + EXPECT_EQ(2, m.size()); + std::unordered_map hm; + hm = absl::StrSplit("a,b,c", ','); + EXPECT_EQ(2, hm.size()); + } +} + +absl::string_view ReturnStringView() { return "Hello World"; } +const char* ReturnConstCharP() { return "Hello World"; } +char* ReturnCharP() { return const_cast("Hello World"); } + +TEST(Split, AcceptsCertainTemporaries) { + std::vector v; + v = absl::StrSplit(ReturnStringView(), ' '); + EXPECT_THAT(v, ElementsAre("Hello", "World")); + v = absl::StrSplit(ReturnConstCharP(), ' '); + EXPECT_THAT(v, ElementsAre("Hello", "World")); + v = absl::StrSplit(ReturnCharP(), ' '); + EXPECT_THAT(v, ElementsAre("Hello", "World")); +} + +TEST(Split, Temporary) { + // Use a std::string longer than the small-std::string-optimization length, so that when + // the temporary is destroyed, if the splitter keeps a reference to the + // std::string's contents, it'll reference freed memory instead of just dead + // on-stack memory. + const char input[] = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u"; + EXPECT_LT(sizeof(std::string), ABSL_ARRAYSIZE(input)) + << "Input should be larger than fits on the stack."; + + // This happens more often in C++11 as part of a range-based for loop. + auto splitter = absl::StrSplit(std::string(input), ','); + std::string expected = "a"; + for (absl::string_view letter : splitter) { + EXPECT_EQ(expected, letter); + ++expected[0]; + } + EXPECT_EQ("v", expected); + + // This happens more often in C++11 as part of a range-based for loop. + auto std_splitter = absl::StrSplit(std::string(input), ','); + expected = "a"; + for (absl::string_view letter : std_splitter) { + EXPECT_EQ(expected, letter); + ++expected[0]; + } + EXPECT_EQ("v", expected); +} + +template +static std::unique_ptr CopyToHeap(const T& value) { + return std::unique_ptr(new T(value)); +} + +TEST(Split, LvalueCaptureIsCopyable) { + std::string input = "a,b"; + auto heap_splitter = CopyToHeap(absl::StrSplit(input, ',')); + auto stack_splitter = *heap_splitter; + heap_splitter.reset(); + std::vector result = stack_splitter; + EXPECT_THAT(result, testing::ElementsAre("a", "b")); +} + +TEST(Split, TemporaryCaptureIsCopyable) { + auto heap_splitter = CopyToHeap(absl::StrSplit(std::string("a,b"), ',')); + auto stack_splitter = *heap_splitter; + heap_splitter.reset(); + std::vector result = stack_splitter; + EXPECT_THAT(result, testing::ElementsAre("a", "b")); +} + +TEST(Split, SplitterIsCopyableAndMoveable) { + auto a = absl::StrSplit("foo", '-'); + + // Ensures that the following expressions compile. + auto b = a; // Copy construct + auto c = std::move(a); // Move construct + b = c; // Copy assign + c = std::move(b); // Move assign + + EXPECT_THAT(c, ElementsAre("foo")); +} + +TEST(Split, StringDelimiter) { + { + std::vector v = absl::StrSplit("a,b", ','); + EXPECT_THAT(v, ElementsAre("a", "b")); + } + + { + std::vector v = absl::StrSplit("a,b", std::string(",")); + EXPECT_THAT(v, ElementsAre("a", "b")); + } + + { + std::vector v = + absl::StrSplit("a,b", absl::string_view(",")); + EXPECT_THAT(v, ElementsAre("a", "b")); + } +} + +TEST(Split, UTF8) { + // Tests splitting utf8 strings and utf8 delimiters. + std::string utf8_string = "\u03BA\u1F79\u03C3\u03BC\u03B5"; + { + // A utf8 input std::string with an ascii delimiter. + std::string to_split = "a," + utf8_string; + std::vector v = absl::StrSplit(to_split, ','); + EXPECT_THAT(v, ElementsAre("a", utf8_string)); + } + + { + // A utf8 input std::string and a utf8 delimiter. + std::string to_split = "a," + utf8_string + ",b"; + std::string unicode_delimiter = "," + utf8_string + ","; + std::vector v = + absl::StrSplit(to_split, unicode_delimiter); + EXPECT_THAT(v, ElementsAre("a", "b")); + } + + { + // A utf8 input std::string and ByAnyChar with ascii chars. + std::vector v = + absl::StrSplit("Foo h\u00E4llo th\u4E1Ere", absl::ByAnyChar(" \t")); + EXPECT_THAT(v, ElementsAre("Foo", "h\u00E4llo", "th\u4E1Ere")); + } +} + +TEST(Split, EmptyStringDelimiter) { + { + std::vector v = absl::StrSplit("", ""); + EXPECT_THAT(v, ElementsAre("")); + } + + { + std::vector v = absl::StrSplit("a", ""); + EXPECT_THAT(v, ElementsAre("a")); + } + + { + std::vector v = absl::StrSplit("ab", ""); + EXPECT_THAT(v, ElementsAre("a", "b")); + } + + { + std::vector v = absl::StrSplit("a b", ""); + EXPECT_THAT(v, ElementsAre("a", " ", "b")); + } +} + +TEST(Split, SubstrDelimiter) { + std::vector results; + absl::string_view delim("//"); + + results = absl::StrSplit("", delim); + EXPECT_THAT(results, ElementsAre("")); + + results = absl::StrSplit("//", delim); + EXPECT_THAT(results, ElementsAre("", "")); + + results = absl::StrSplit("ab", delim); + EXPECT_THAT(results, ElementsAre("ab")); + + results = absl::StrSplit("ab//", delim); + EXPECT_THAT(results, ElementsAre("ab", "")); + + results = absl::StrSplit("ab/", delim); + EXPECT_THAT(results, ElementsAre("ab/")); + + results = absl::StrSplit("a/b", delim); + EXPECT_THAT(results, ElementsAre("a/b")); + + results = absl::StrSplit("a//b", delim); + EXPECT_THAT(results, ElementsAre("a", "b")); + + results = absl::StrSplit("a///b", delim); + EXPECT_THAT(results, ElementsAre("a", "/b")); + + results = absl::StrSplit("a////b", delim); + EXPECT_THAT(results, ElementsAre("a", "", "b")); +} + +TEST(Split, EmptyResults) { + std::vector results; + + results = absl::StrSplit("", '#'); + EXPECT_THAT(results, ElementsAre("")); + + results = absl::StrSplit("#", '#'); + EXPECT_THAT(results, ElementsAre("", "")); + + results = absl::StrSplit("#cd", '#'); + EXPECT_THAT(results, ElementsAre("", "cd")); + + results = absl::StrSplit("ab#cd#", '#'); + EXPECT_THAT(results, ElementsAre("ab", "cd", "")); + + results = absl::StrSplit("ab##cd", '#'); + EXPECT_THAT(results, ElementsAre("ab", "", "cd")); + + results = absl::StrSplit("ab##", '#'); + EXPECT_THAT(results, ElementsAre("ab", "", "")); + + results = absl::StrSplit("ab#ab#", '#'); + EXPECT_THAT(results, ElementsAre("ab", "ab", "")); + + results = absl::StrSplit("aaaa", 'a'); + EXPECT_THAT(results, ElementsAre("", "", "", "", "")); + + results = absl::StrSplit("", '#', absl::SkipEmpty()); + EXPECT_THAT(results, ElementsAre()); +} + +template +static bool IsFoundAtStartingPos(absl::string_view text, Delimiter d, + size_t starting_pos, int expected_pos) { + absl::string_view found = d.Find(text, starting_pos); + return found.data() != text.end() && + expected_pos == found.data() - text.data(); +} + +// Helper function for testing Delimiter objects. Returns true if the given +// Delimiter is found in the given std::string at the given position. This function +// tests two cases: +// 1. The actual text given, staring at position 0 +// 2. The text given with leading padding that should be ignored +template +static bool IsFoundAt(absl::string_view text, Delimiter d, int expected_pos) { + const std::string leading_text = ",x,y,z,"; + return IsFoundAtStartingPos(text, d, 0, expected_pos) && + IsFoundAtStartingPos(leading_text + std::string(text), d, + leading_text.length(), + expected_pos + leading_text.length()); +} + +// +// Tests for Literal +// + +// Tests using any delimiter that represents a single comma. +template +void TestComma(Delimiter d) { + EXPECT_TRUE(IsFoundAt(",", d, 0)); + EXPECT_TRUE(IsFoundAt("a,", d, 1)); + EXPECT_TRUE(IsFoundAt(",b", d, 0)); + EXPECT_TRUE(IsFoundAt("a,b", d, 1)); + EXPECT_TRUE(IsFoundAt("a,b,", d, 1)); + EXPECT_TRUE(IsFoundAt("a,b,c", d, 1)); + EXPECT_FALSE(IsFoundAt("", d, -1)); + EXPECT_FALSE(IsFoundAt(" ", d, -1)); + EXPECT_FALSE(IsFoundAt("a", d, -1)); + EXPECT_FALSE(IsFoundAt("a b c", d, -1)); + EXPECT_FALSE(IsFoundAt("a;b;c", d, -1)); + EXPECT_FALSE(IsFoundAt(";", d, -1)); +} + +TEST(Delimiter, Literal) { + using absl::ByString; + TestComma(ByString(",")); + + // Works as named variable. + ByString comma_string(","); + TestComma(comma_string); + + // The first occurrence of empty std::string ("") in a std::string is at position 0. + // There is a test below that demonstrates this for absl::string_view::find(). + // If the ByString delimiter returned position 0 for this, there would + // be an infinite loop in the SplitIterator code. To avoid this, empty std::string + // is a special case in that it always returns the item at position 1. + absl::string_view abc("abc"); + EXPECT_EQ(0, abc.find("")); // "" is found at position 0 + ByString empty(""); + EXPECT_FALSE(IsFoundAt("", empty, 0)); + EXPECT_FALSE(IsFoundAt("a", empty, 0)); + EXPECT_TRUE(IsFoundAt("ab", empty, 1)); + EXPECT_TRUE(IsFoundAt("abc", empty, 1)); +} + +TEST(Split, ByChar) { + using absl::ByChar; + TestComma(ByChar(',')); + + // Works as named variable. + ByChar comma_char(','); + TestComma(comma_char); +} + +// +// Tests for ByAnyChar +// + +TEST(Delimiter, ByAnyChar) { + using absl::ByAnyChar; + ByAnyChar one_delim(","); + // Found + EXPECT_TRUE(IsFoundAt(",", one_delim, 0)); + EXPECT_TRUE(IsFoundAt("a,", one_delim, 1)); + EXPECT_TRUE(IsFoundAt("a,b", one_delim, 1)); + EXPECT_TRUE(IsFoundAt(",b", one_delim, 0)); + // Not found + EXPECT_FALSE(IsFoundAt("", one_delim, -1)); + EXPECT_FALSE(IsFoundAt(" ", one_delim, -1)); + EXPECT_FALSE(IsFoundAt("a", one_delim, -1)); + EXPECT_FALSE(IsFoundAt("a;b;c", one_delim, -1)); + EXPECT_FALSE(IsFoundAt(";", one_delim, -1)); + + ByAnyChar two_delims(",;"); + // Found + EXPECT_TRUE(IsFoundAt(",", two_delims, 0)); + EXPECT_TRUE(IsFoundAt(";", two_delims, 0)); + EXPECT_TRUE(IsFoundAt(",;", two_delims, 0)); + EXPECT_TRUE(IsFoundAt(";,", two_delims, 0)); + EXPECT_TRUE(IsFoundAt(",;b", two_delims, 0)); + EXPECT_TRUE(IsFoundAt(";,b", two_delims, 0)); + EXPECT_TRUE(IsFoundAt("a;,", two_delims, 1)); + EXPECT_TRUE(IsFoundAt("a,;", two_delims, 1)); + EXPECT_TRUE(IsFoundAt("a;,b", two_delims, 1)); + EXPECT_TRUE(IsFoundAt("a,;b", two_delims, 1)); + // Not found + EXPECT_FALSE(IsFoundAt("", two_delims, -1)); + EXPECT_FALSE(IsFoundAt(" ", two_delims, -1)); + EXPECT_FALSE(IsFoundAt("a", two_delims, -1)); + EXPECT_FALSE(IsFoundAt("a=b=c", two_delims, -1)); + EXPECT_FALSE(IsFoundAt("=", two_delims, -1)); + + // ByAnyChar behaves just like ByString when given a delimiter of empty + // std::string. That is, it always returns a zero-length absl::string_view + // referring to the item at position 1, not position 0. + ByAnyChar empty(""); + EXPECT_FALSE(IsFoundAt("", empty, 0)); + EXPECT_FALSE(IsFoundAt("a", empty, 0)); + EXPECT_TRUE(IsFoundAt("ab", empty, 1)); + EXPECT_TRUE(IsFoundAt("abc", empty, 1)); +} + +// +// Tests for ByLength +// + +TEST(Delimiter, ByLength) { + using absl::ByLength; + + ByLength four_char_delim(4); + + // Found + EXPECT_TRUE(IsFoundAt("abcde", four_char_delim, 4)); + EXPECT_TRUE(IsFoundAt("abcdefghijklmnopqrstuvwxyz", four_char_delim, 4)); + EXPECT_TRUE(IsFoundAt("a b,c\nd", four_char_delim, 4)); + // Not found + EXPECT_FALSE(IsFoundAt("", four_char_delim, 0)); + EXPECT_FALSE(IsFoundAt("a", four_char_delim, 0)); + EXPECT_FALSE(IsFoundAt("ab", four_char_delim, 0)); + EXPECT_FALSE(IsFoundAt("abc", four_char_delim, 0)); + EXPECT_FALSE(IsFoundAt("abcd", four_char_delim, 0)); +} + +TEST(Split, WorksWithLargeStrings) { + if (sizeof(size_t) > 4) { + std::string s((uint32_t{1} << 31) + 1, 'x'); // 2G + 1 byte + s.back() = '-'; + std::vector v = absl::StrSplit(s, '-'); + EXPECT_EQ(2, v.size()); + // The first element will contain 2G of 'x's. + // testing::StartsWith is too slow with a 2G std::string. + EXPECT_EQ('x', v[0][0]); + EXPECT_EQ('x', v[0][1]); + EXPECT_EQ('x', v[0][3]); + EXPECT_EQ("", v[1]); + } +} + +TEST(SplitInternalTest, TypeTraits) { + EXPECT_FALSE(absl::strings_internal::HasMappedType::value); + EXPECT_TRUE( + (absl::strings_internal::HasMappedType>::value)); + EXPECT_FALSE(absl::strings_internal::HasValueType::value); + EXPECT_TRUE( + (absl::strings_internal::HasValueType>::value)); + EXPECT_FALSE(absl::strings_internal::HasConstIterator::value); + EXPECT_TRUE( + (absl::strings_internal::HasConstIterator>::value)); + EXPECT_FALSE(absl::strings_internal::IsInitializerList::value); + EXPECT_TRUE((absl::strings_internal::IsInitializerList< + std::initializer_list>::value)); +} + +} // namespace From f38714732266d55f88a2e839bdfd040110a76691 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 14:10:31 -0500 Subject: [PATCH 08/48] absl dependencies --- .../abseil-cpp/absl/numeric/CMakeLists.txt | 62 + .../abseil-cpp/absl/numeric/int128.cc | 216 +++ .../abseil-cpp/absl/numeric/int128.h | 617 +++++++++ .../absl/numeric/int128_have_intrinsic.inc | 18 + .../absl/numeric/int128_no_intrinsic.inc | 18 + .../absl/numeric/int128_stream_test.cc | 666 +++++++++ .../abseil-cpp/absl/numeric/int128_test.cc | 431 ++++++ .../absl/strings/internal/ostringstream.cc | 34 + .../absl/strings/internal/ostringstream.h | 87 ++ .../strings/internal/ostringstream_test.cc | 102 ++ .../absl/strings/internal/str_join_internal.h | 309 +++++ .../strings/internal/str_split_internal.h | 435 ++++++ .../abseil-cpp/absl/strings/numbers.cc | 919 +++++++++++++ .../abseil-cpp/absl/strings/numbers.h | 172 +++ .../abseil-cpp/absl/strings/numbers_test.cc | 1186 +++++++++++++++++ .../abseil-cpp/absl/strings/str_cat.cc | 208 +++ .../abseil-cpp/absl/strings/str_cat.h | 347 +++++ .../abseil-cpp/absl/strings/str_cat_test.cc | 468 +++++++ .../abseil-cpp/absl/strings/strip.cc | 1 + .../abseil-cpp/absl/strings/strip.h | 89 ++ .../abseil-cpp/absl/strings/strip_test.cc | 201 +++ 21 files changed, 6586 insertions(+) create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/CMakeLists.txt create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128.h create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128_have_intrinsic.inc create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128_no_intrinsic.inc create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128_stream_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/internal/str_join_internal.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/internal/str_split_internal.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/numbers.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/numbers.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/numbers_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_cat.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_cat.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_cat_test.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/strip.cc create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/strip.h create mode 100644 Firestore/third_party/abseil-cpp/absl/strings/strip_test.cc diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/numeric/CMakeLists.txt new file mode 100644 index 00000000000..3360b2ee726 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/CMakeLists.txt @@ -0,0 +1,62 @@ +# +# Copyright 2017 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +list(APPEND NUMERIC_PUBLIC_HEADERS + "int128.h" +) + + +# library 128 +list(APPEND INT128_SRC + "int128.cc" + ${NUMERIC_PUBLIC_HEADERS} +) +absl_library( + TARGET + absl_int128 + SOURCES + ${INT128_SRC} + PUBLIC_LIBRARIES + ${INT128_PUBLIC_LIBRARIES} + EXPORT_NAME + int128 +) + + +absl_header_library( + TARGET + absl_numeric + PUBLIC_LIBRARIES + absl::int128 + EXPORT_NAME + numeric +) + +# test int128_test +set(INT128_TEST_SRC "int128_test.cc") +set(INT128_TEST_PUBLIC_LIBRARIES absl::numeric absl::base) + +absl_test( + TARGET + int128_test + SOURCES + ${INT128_TEST_SRC} + PUBLIC_LIBRARIES + ${INT128_TEST_PUBLIC_LIBRARIES} +) + + + diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128.cc b/Firestore/third_party/abseil-cpp/absl/numeric/int128.cc new file mode 100644 index 00000000000..b32d8095fdf --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128.cc @@ -0,0 +1,216 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/numeric/int128.h" + +#include +#include +#include +#include // NOLINT(readability/streams) +#include +#include + +namespace absl { + +const uint128 kuint128max = MakeUint128(std::numeric_limits::max(), + std::numeric_limits::max()); + +namespace { + +// Returns the 0-based position of the last set bit (i.e., most significant bit) +// in the given uint64_t. The argument may not be 0. +// +// For example: +// Given: 5 (decimal) == 101 (binary) +// Returns: 2 +#define STEP(T, n, pos, sh) \ + do { \ + if ((n) >= (static_cast(1) << (sh))) { \ + (n) = (n) >> (sh); \ + (pos) |= (sh); \ + } \ + } while (0) +static inline int Fls64(uint64_t n) { + assert(n != 0); + int pos = 0; + STEP(uint64_t, n, pos, 0x20); + uint32_t n32 = static_cast(n); + STEP(uint32_t, n32, pos, 0x10); + STEP(uint32_t, n32, pos, 0x08); + STEP(uint32_t, n32, pos, 0x04); + return pos + ((uint64_t{0x3333333322221100} >> (n32 << 2)) & 0x3); +} +#undef STEP + +// Like Fls64() above, but returns the 0-based position of the last set bit +// (i.e., most significant bit) in the given uint128. The argument may not be 0. +static inline int Fls128(uint128 n) { + if (uint64_t hi = Uint128High64(n)) { + return Fls64(hi) + 64; + } + return Fls64(Uint128Low64(n)); +} + +// Long division/modulo for uint128 implemented using the shift-subtract +// division algorithm adapted from: +// http://stackoverflow.com/questions/5386377/division-without-using +void DivModImpl(uint128 dividend, uint128 divisor, uint128* quotient_ret, + uint128* remainder_ret) { + assert(divisor != 0); + + if (divisor > dividend) { + *quotient_ret = 0; + *remainder_ret = dividend; + return; + } + + if (divisor == dividend) { + *quotient_ret = 1; + *remainder_ret = 0; + return; + } + + uint128 denominator = divisor; + uint128 quotient = 0; + + // Left aligns the MSB of the denominator and the dividend. + const int shift = Fls128(dividend) - Fls128(denominator); + denominator <<= shift; + + // Uses shift-subtract algorithm to divide dividend by denominator. The + // remainder will be left in dividend. + for (int i = 0; i <= shift; ++i) { + quotient <<= 1; + if (dividend >= denominator) { + dividend -= denominator; + quotient |= 1; + } + denominator >>= 1; + } + + *quotient_ret = quotient; + *remainder_ret = dividend; +} + +template +uint128 MakeUint128FromFloat(T v) { + static_assert(std::is_floating_point::value, ""); + + // Rounding behavior is towards zero, same as for built-in types. + + // Undefined behavior if v is NaN or cannot fit into uint128. + assert(std::isfinite(v) && v > -1 && + (std::numeric_limits::max_exponent <= 128 || + v < std::ldexp(static_cast(1), 128))); + + if (v >= std::ldexp(static_cast(1), 64)) { + uint64_t hi = static_cast(std::ldexp(v, -64)); + uint64_t lo = static_cast(v - std::ldexp(static_cast(hi), 64)); + return MakeUint128(hi, lo); + } + + return MakeUint128(0, static_cast(v)); +} +} // namespace + +uint128::uint128(float v) : uint128(MakeUint128FromFloat(v)) {} +uint128::uint128(double v) : uint128(MakeUint128FromFloat(v)) {} +uint128::uint128(long double v) : uint128(MakeUint128FromFloat(v)) {} + +uint128& uint128::operator/=(uint128 other) { + uint128 quotient = 0; + uint128 remainder = 0; + DivModImpl(*this, other, "ient, &remainder); + *this = quotient; + return *this; +} +uint128& uint128::operator%=(uint128 other) { + uint128 quotient = 0; + uint128 remainder = 0; + DivModImpl(*this, other, "ient, &remainder); + *this = remainder; + return *this; +} + +namespace { + +std::string Uint128ToFormattedString(uint128 v, std::ios_base::fmtflags flags) { + // Select a divisor which is the largest power of the base < 2^64. + uint128 div; + int div_base_log; + switch (flags & std::ios::basefield) { + case std::ios::hex: + div = 0x1000000000000000; // 16^15 + div_base_log = 15; + break; + case std::ios::oct: + div = 01000000000000000000000; // 8^21 + div_base_log = 21; + break; + default: // std::ios::dec + div = 10000000000000000000u; // 10^19 + div_base_log = 19; + break; + } + + // Now piece together the uint128 representation from three chunks of the + // original value, each less than "div" and therefore representable as a + // uint64_t. + std::ostringstream os; + std::ios_base::fmtflags copy_mask = + std::ios::basefield | std::ios::showbase | std::ios::uppercase; + os.setf(flags & copy_mask, copy_mask); + uint128 high = v; + uint128 low; + DivModImpl(high, div, &high, &low); + uint128 mid; + DivModImpl(high, div, &high, &mid); + if (Uint128Low64(high) != 0) { + os << Uint128Low64(high); + os << std::noshowbase << std::setfill('0') << std::setw(div_base_log); + os << Uint128Low64(mid); + os << std::setw(div_base_log); + } else if (Uint128Low64(mid) != 0) { + os << Uint128Low64(mid); + os << std::noshowbase << std::setfill('0') << std::setw(div_base_log); + } + os << Uint128Low64(low); + return os.str(); +} + +} // namespace + +std::ostream& operator<<(std::ostream& os, uint128 v) { + std::ios_base::fmtflags flags = os.flags(); + std::string rep = Uint128ToFormattedString(v, flags); + + // Add the requisite padding. + std::streamsize width = os.width(0); + if (static_cast(width) > rep.size()) { + std::ios::fmtflags adjustfield = flags & std::ios::adjustfield; + if (adjustfield == std::ios::left) { + rep.append(width - rep.size(), os.fill()); + } else if (adjustfield == std::ios::internal && + (flags & std::ios::showbase) && + (flags & std::ios::basefield) == std::ios::hex && v != 0) { + rep.insert(2, width - rep.size(), os.fill()); + } else { + rep.insert(0, width - rep.size(), os.fill()); + } + } + + return os << rep; +} + +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128.h b/Firestore/third_party/abseil-cpp/absl/numeric/int128.h new file mode 100644 index 00000000000..5e1b3f03c0f --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128.h @@ -0,0 +1,617 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: int128.h +// ----------------------------------------------------------------------------- +// +// This header file defines 128-bit integer types. Currently, this file defines +// `uint128`, an unsigned 128-bit integer; a signed 128-bit integer is +// forthcoming. + +#ifndef ABSL_NUMERIC_INT128_H_ +#define ABSL_NUMERIC_INT128_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/base/port.h" + +namespace absl { + +// uint128 +// +// An unsigned 128-bit integer type. The API is meant to mimic an intrinsic type +// as closely as is practical, including exhibiting undefined behavior in +// analogous cases (e.g. division by zero). This type is intended to be a +// drop-in replacement once C++ supports an intrinsic `uint128_t` type; when +// that occurs, existing uses of `uint128` will continue to work using that new +// type. +// +// Note: code written with this type will continue to compile once `uint128_t` +// is introduced, provided the replacement helper functions +// `Uint128(Low|High)64()` and `MakeUint128()` are made. +// +// A `uint128` supports the following: +// +// * Implicit construction from integral types +// * Explicit conversion to integral types +// +// Additionally, if your compiler supports `__int128`, `uint128` is +// interoperable with that type. (Abseil checks for this compatibility through +// the `ABSL_HAVE_INTRINSIC_INT128` macro.) +// +// However, a `uint128` differs from intrinsic integral types in the following +// ways: +// +// * Errors on implicit conversions that do not preserve value (such as +// loss of precision when converting to float values). +// * Requires explicit construction from and conversion to floating point +// types. +// * Conversion to integral types requires an explicit static_cast() to +// mimic use of the `-Wnarrowing` compiler flag. +// +// Example: +// +// float y = absl::Uint128Max(); // Error. uint128 cannot be implicitly +// // converted to float. +// +// absl::uint128 v; +// absl::uint64_t i = v; // Error +// absl::uint64_t i = static_cast(v); // OK +// +class alignas(16) uint128 { + public: + uint128() = default; + + // Constructors from arithmetic types + constexpr uint128(int v); // NOLINT(runtime/explicit) + constexpr uint128(unsigned int v); // NOLINT(runtime/explicit) + constexpr uint128(long v); // NOLINT(runtime/int) + constexpr uint128(unsigned long v); // NOLINT(runtime/int) + constexpr uint128(long long v); // NOLINT(runtime/int) + constexpr uint128(unsigned long long v); // NOLINT(runtime/int) +#ifdef ABSL_HAVE_INTRINSIC_INT128 + constexpr uint128(__int128 v); // NOLINT(runtime/explicit) + constexpr uint128(unsigned __int128 v); // NOLINT(runtime/explicit) +#endif // ABSL_HAVE_INTRINSIC_INT128 + explicit uint128(float v); + explicit uint128(double v); + explicit uint128(long double v); + + // Assignment operators from arithmetic types + uint128& operator=(int v); + uint128& operator=(unsigned int v); + uint128& operator=(long v); // NOLINT(runtime/int) + uint128& operator=(unsigned long v); // NOLINT(runtime/int) + uint128& operator=(long long v); // NOLINT(runtime/int) + uint128& operator=(unsigned long long v); // NOLINT(runtime/int) +#ifdef ABSL_HAVE_INTRINSIC_INT128 + uint128& operator=(__int128 v); + uint128& operator=(unsigned __int128 v); +#endif // ABSL_HAVE_INTRINSIC_INT128 + + // Conversion operators to other arithmetic types + constexpr explicit operator bool() const; + constexpr explicit operator char() const; + constexpr explicit operator signed char() const; + constexpr explicit operator unsigned char() const; + constexpr explicit operator char16_t() const; + constexpr explicit operator char32_t() const; + constexpr explicit operator wchar_t() const; + constexpr explicit operator short() const; // NOLINT(runtime/int) + // NOLINTNEXTLINE(runtime/int) + constexpr explicit operator unsigned short() const; + constexpr explicit operator int() const; + constexpr explicit operator unsigned int() const; + constexpr explicit operator long() const; // NOLINT(runtime/int) + // NOLINTNEXTLINE(runtime/int) + constexpr explicit operator unsigned long() const; + // NOLINTNEXTLINE(runtime/int) + constexpr explicit operator long long() const; + // NOLINTNEXTLINE(runtime/int) + constexpr explicit operator unsigned long long() const; +#ifdef ABSL_HAVE_INTRINSIC_INT128 + constexpr explicit operator __int128() const; + constexpr explicit operator unsigned __int128() const; +#endif // ABSL_HAVE_INTRINSIC_INT128 + explicit operator float() const; + explicit operator double() const; + explicit operator long double() const; + + // Trivial copy constructor, assignment operator and destructor. + + // Arithmetic operators. + uint128& operator+=(uint128 other); + uint128& operator-=(uint128 other); + uint128& operator*=(uint128 other); + // Long division/modulo for uint128. + uint128& operator/=(uint128 other); + uint128& operator%=(uint128 other); + uint128 operator++(int); + uint128 operator--(int); + uint128& operator<<=(int); + uint128& operator>>=(int); + uint128& operator&=(uint128 other); + uint128& operator|=(uint128 other); + uint128& operator^=(uint128 other); + uint128& operator++(); + uint128& operator--(); + + // Uint128Low64() + // + // Returns the lower 64-bit value of a `uint128` value. + friend constexpr uint64_t Uint128Low64(uint128 v); + + // Uint128High64() + // + // Returns the higher 64-bit value of a `uint128` value. + friend constexpr uint64_t Uint128High64(uint128 v); + + // MakeUInt128() + // + // Constructs a `uint128` numeric value from two 64-bit unsigned integers. + // Note that this factory function is the only way to construct a `uint128` + // from integer values greater than 2^64. + // + // Example: + // + // absl::uint128 big = absl::MakeUint128(1, 0); + friend constexpr uint128 MakeUint128(uint64_t high, uint64_t low); + + // Uint128Max() + // + // Returns the highest value for a 128-bit unsigned integer. + friend constexpr uint128 Uint128Max(); + + private: + constexpr uint128(uint64_t high, uint64_t low); + + // TODO(strel) Update implementation to use __int128 once all users of + // uint128 are fixed to not depend on alignof(uint128) == 8. Also add + // alignas(16) to class definition to keep alignment consistent across + // platforms. +#if defined(ABSL_IS_LITTLE_ENDIAN) + uint64_t lo_; + uint64_t hi_; +#elif defined(ABSL_IS_BIG_ENDIAN) + uint64_t hi_; + uint64_t lo_; +#else // byte order +#error "Unsupported byte order: must be little-endian or big-endian." +#endif // byte order +}; + +// Prefer to use the constexpr `Uint128Max()`. +// +// TODO(absl-team) deprecate kuint128max once migration tool is released. +extern const uint128 kuint128max; + +// allow uint128 to be logged +extern std::ostream& operator<<(std::ostream& os, uint128 v); + +// TODO(strel) add operator>>(std::istream&, uint128) + +// TODO(absl-team): Implement signed 128-bit type + +// -------------------------------------------------------------------------- +// Implementation details follow +// -------------------------------------------------------------------------- + +constexpr uint128 MakeUint128(uint64_t high, uint64_t low) { + return uint128(high, low); +} + +constexpr uint128 Uint128Max() { + return uint128(std::numeric_limits::max(), + std::numeric_limits::max()); +} + +// Assignment from integer types. + +inline uint128& uint128::operator=(int v) { return *this = uint128(v); } + +inline uint128& uint128::operator=(unsigned int v) { + return *this = uint128(v); +} + +inline uint128& uint128::operator=(long v) { // NOLINT(runtime/int) + return *this = uint128(v); +} + +// NOLINTNEXTLINE(runtime/int) +inline uint128& uint128::operator=(unsigned long v) { + return *this = uint128(v); +} + +// NOLINTNEXTLINE(runtime/int) +inline uint128& uint128::operator=(long long v) { + return *this = uint128(v); +} + +// NOLINTNEXTLINE(runtime/int) +inline uint128& uint128::operator=(unsigned long long v) { + return *this = uint128(v); +} + +#ifdef ABSL_HAVE_INTRINSIC_INT128 +inline uint128& uint128::operator=(__int128 v) { + return *this = uint128(v); +} + +inline uint128& uint128::operator=(unsigned __int128 v) { + return *this = uint128(v); +} +#endif // ABSL_HAVE_INTRINSIC_INT128 + +// Shift and arithmetic operators. + +inline uint128 operator<<(uint128 lhs, int amount) { return lhs <<= amount; } + +inline uint128 operator>>(uint128 lhs, int amount) { return lhs >>= amount; } + +inline uint128 operator+(uint128 lhs, uint128 rhs) { return lhs += rhs; } + +inline uint128 operator-(uint128 lhs, uint128 rhs) { return lhs -= rhs; } + +inline uint128 operator*(uint128 lhs, uint128 rhs) { return lhs *= rhs; } + +inline uint128 operator/(uint128 lhs, uint128 rhs) { return lhs /= rhs; } + +inline uint128 operator%(uint128 lhs, uint128 rhs) { return lhs %= rhs; } + +constexpr uint64_t Uint128Low64(uint128 v) { return v.lo_; } + +constexpr uint64_t Uint128High64(uint128 v) { return v.hi_; } + +// Constructors from integer types. + +#if defined(ABSL_IS_LITTLE_ENDIAN) + +constexpr uint128::uint128(uint64_t high, uint64_t low) + : lo_(low), hi_(high) {} + +constexpr uint128::uint128(int v) + : lo_(v), hi_(v < 0 ? std::numeric_limits::max() : 0) {} +constexpr uint128::uint128(long v) // NOLINT(runtime/int) + : lo_(v), hi_(v < 0 ? std::numeric_limits::max() : 0) {} +constexpr uint128::uint128(long long v) // NOLINT(runtime/int) + : lo_(v), hi_(v < 0 ? std::numeric_limits::max() : 0) {} + +constexpr uint128::uint128(unsigned int v) : lo_(v), hi_(0) {} +// NOLINTNEXTLINE(runtime/int) +constexpr uint128::uint128(unsigned long v) : lo_(v), hi_(0) {} +// NOLINTNEXTLINE(runtime/int) +constexpr uint128::uint128(unsigned long long v) : lo_(v), hi_(0) {} + +#ifdef ABSL_HAVE_INTRINSIC_INT128 +constexpr uint128::uint128(__int128 v) + : lo_(static_cast(v & ~uint64_t{0})), + hi_(static_cast(static_cast(v) >> 64)) {} +constexpr uint128::uint128(unsigned __int128 v) + : lo_(static_cast(v & ~uint64_t{0})), + hi_(static_cast(v >> 64)) {} +#endif // ABSL_HAVE_INTRINSIC_INT128 + +#elif defined(ABSL_IS_BIG_ENDIAN) + +constexpr uint128::uint128(uint64_t high, uint64_t low) + : hi_(high), lo_(low) {} + +constexpr uint128::uint128(int v) + : hi_(v < 0 ? std::numeric_limits::max() : 0), lo_(v) {} +constexpr uint128::uint128(long v) // NOLINT(runtime/int) + : hi_(v < 0 ? std::numeric_limits::max() : 0), lo_(v) {} +constexpr uint128::uint128(long long v) // NOLINT(runtime/int) + : hi_(v < 0 ? std::numeric_limits::max() : 0), lo_(v) {} + +constexpr uint128::uint128(unsigned int v) : hi_(0), lo_(v) {} +// NOLINTNEXTLINE(runtime/int) +constexpr uint128::uint128(unsigned long v) : hi_(0), lo_(v) {} +// NOLINTNEXTLINE(runtime/int) +constexpr uint128::uint128(unsigned long long v) : hi_(0), lo_(v) {} + +#ifdef ABSL_HAVE_INTRINSIC_INT128 +constexpr uint128::uint128(__int128 v) + : hi_(static_cast(static_cast(v) >> 64)), + lo_(static_cast(v & ~uint64_t{0})) {} +constexpr uint128::uint128(unsigned __int128 v) + : hi_(static_cast(v >> 64)), + lo_(static_cast(v & ~uint64_t{0})) {} +#endif // ABSL_HAVE_INTRINSIC_INT128 + +#else // byte order +#error "Unsupported byte order: must be little-endian or big-endian." +#endif // byte order + +// Conversion operators to integer types. + +constexpr uint128::operator bool() const { return lo_ || hi_; } + +constexpr uint128::operator char() const { return static_cast(lo_); } + +constexpr uint128::operator signed char() const { + return static_cast(lo_); +} + +constexpr uint128::operator unsigned char() const { + return static_cast(lo_); +} + +constexpr uint128::operator char16_t() const { + return static_cast(lo_); +} + +constexpr uint128::operator char32_t() const { + return static_cast(lo_); +} + +constexpr uint128::operator wchar_t() const { + return static_cast(lo_); +} + +// NOLINTNEXTLINE(runtime/int) +constexpr uint128::operator short() const { return static_cast(lo_); } + +constexpr uint128::operator unsigned short() const { // NOLINT(runtime/int) + return static_cast(lo_); // NOLINT(runtime/int) +} + +constexpr uint128::operator int() const { return static_cast(lo_); } + +constexpr uint128::operator unsigned int() const { + return static_cast(lo_); +} + +// NOLINTNEXTLINE(runtime/int) +constexpr uint128::operator long() const { return static_cast(lo_); } + +constexpr uint128::operator unsigned long() const { // NOLINT(runtime/int) + return static_cast(lo_); // NOLINT(runtime/int) +} + +constexpr uint128::operator long long() const { // NOLINT(runtime/int) + return static_cast(lo_); // NOLINT(runtime/int) +} + +constexpr uint128::operator unsigned long long() const { // NOLINT(runtime/int) + return static_cast(lo_); // NOLINT(runtime/int) +} + +#ifdef ABSL_HAVE_INTRINSIC_INT128 +constexpr uint128::operator __int128() const { + return (static_cast<__int128>(hi_) << 64) + lo_; +} + +constexpr uint128::operator unsigned __int128() const { + return (static_cast(hi_) << 64) + lo_; +} +#endif // ABSL_HAVE_INTRINSIC_INT128 + +// Conversion operators to floating point types. + +inline uint128::operator float() const { + return static_cast(lo_) + std::ldexp(static_cast(hi_), 64); +} + +inline uint128::operator double() const { + return static_cast(lo_) + std::ldexp(static_cast(hi_), 64); +} + +inline uint128::operator long double() const { + return static_cast(lo_) + + std::ldexp(static_cast(hi_), 64); +} + +// Comparison operators. + +inline bool operator==(uint128 lhs, uint128 rhs) { + return (Uint128Low64(lhs) == Uint128Low64(rhs) && + Uint128High64(lhs) == Uint128High64(rhs)); +} + +inline bool operator!=(uint128 lhs, uint128 rhs) { + return !(lhs == rhs); +} + +inline bool operator<(uint128 lhs, uint128 rhs) { + return (Uint128High64(lhs) == Uint128High64(rhs)) + ? (Uint128Low64(lhs) < Uint128Low64(rhs)) + : (Uint128High64(lhs) < Uint128High64(rhs)); +} + +inline bool operator>(uint128 lhs, uint128 rhs) { + return (Uint128High64(lhs) == Uint128High64(rhs)) + ? (Uint128Low64(lhs) > Uint128Low64(rhs)) + : (Uint128High64(lhs) > Uint128High64(rhs)); +} + +inline bool operator<=(uint128 lhs, uint128 rhs) { + return (Uint128High64(lhs) == Uint128High64(rhs)) + ? (Uint128Low64(lhs) <= Uint128Low64(rhs)) + : (Uint128High64(lhs) <= Uint128High64(rhs)); +} + +inline bool operator>=(uint128 lhs, uint128 rhs) { + return (Uint128High64(lhs) == Uint128High64(rhs)) + ? (Uint128Low64(lhs) >= Uint128Low64(rhs)) + : (Uint128High64(lhs) >= Uint128High64(rhs)); +} + +// Unary operators. + +inline uint128 operator-(uint128 val) { + uint64_t hi = ~Uint128High64(val); + uint64_t lo = ~Uint128Low64(val) + 1; + if (lo == 0) ++hi; // carry + return MakeUint128(hi, lo); +} + +inline bool operator!(uint128 val) { + return !Uint128High64(val) && !Uint128Low64(val); +} + +// Logical operators. + +inline uint128 operator~(uint128 val) { + return MakeUint128(~Uint128High64(val), ~Uint128Low64(val)); +} + +inline uint128 operator|(uint128 lhs, uint128 rhs) { + return MakeUint128(Uint128High64(lhs) | Uint128High64(rhs), + Uint128Low64(lhs) | Uint128Low64(rhs)); +} + +inline uint128 operator&(uint128 lhs, uint128 rhs) { + return MakeUint128(Uint128High64(lhs) & Uint128High64(rhs), + Uint128Low64(lhs) & Uint128Low64(rhs)); +} + +inline uint128 operator^(uint128 lhs, uint128 rhs) { + return MakeUint128(Uint128High64(lhs) ^ Uint128High64(rhs), + Uint128Low64(lhs) ^ Uint128Low64(rhs)); +} + +inline uint128& uint128::operator|=(uint128 other) { + hi_ |= other.hi_; + lo_ |= other.lo_; + return *this; +} + +inline uint128& uint128::operator&=(uint128 other) { + hi_ &= other.hi_; + lo_ &= other.lo_; + return *this; +} + +inline uint128& uint128::operator^=(uint128 other) { + hi_ ^= other.hi_; + lo_ ^= other.lo_; + return *this; +} + +// Shift and arithmetic assign operators. + +inline uint128& uint128::operator<<=(int amount) { + assert(amount >= 0); // Negative shifts are undefined. + assert(amount < 128); // Shifts of >= 128 are undefined. + + // uint64_t shifts of >= 64 are undefined, so we will need some + // special-casing. + if (amount < 64) { + if (amount != 0) { + hi_ = (hi_ << amount) | (lo_ >> (64 - amount)); + lo_ = lo_ << amount; + } + } else { + hi_ = lo_ << (amount - 64); + lo_ = 0; + } + return *this; +} + +inline uint128& uint128::operator>>=(int amount) { + assert(amount >= 0); // Negative shifts are undefined. + assert(amount < 128); // Shifts of >= 128 are undefined. + + // uint64_t shifts of >= 64 are undefined, so we will need some + // special-casing. + if (amount < 64) { + if (amount != 0) { + lo_ = (lo_ >> amount) | (hi_ << (64 - amount)); + hi_ = hi_ >> amount; + } + } else { + lo_ = hi_ >> (amount - 64); + hi_ = 0; + } + return *this; +} + +inline uint128& uint128::operator+=(uint128 other) { + hi_ += other.hi_; + uint64_t lolo = lo_ + other.lo_; + if (lolo < lo_) + ++hi_; + lo_ = lolo; + return *this; +} + +inline uint128& uint128::operator-=(uint128 other) { + hi_ -= other.hi_; + if (other.lo_ > lo_) --hi_; + lo_ -= other.lo_; + return *this; +} + +inline uint128& uint128::operator*=(uint128 other) { +#if defined(ABSL_HAVE_INTRINSIC_INT128) + // TODO(strel) Remove once alignment issues are resolved and unsigned __int128 + // can be used for uint128 storage. + *this = static_cast(*this) * + static_cast(other); + return *this; +#else // ABSL_HAVE_INTRINSIC128 + uint64_t a32 = lo_ >> 32; + uint64_t a00 = lo_ & 0xffffffff; + uint64_t b32 = other.lo_ >> 32; + uint64_t b00 = other.lo_ & 0xffffffff; + hi_ = hi_ * other.lo_ + lo_ * other.hi_ + a32 * b32; + lo_ = a00 * b00; + *this += uint128(a32 * b00) << 32; + *this += uint128(a00 * b32) << 32; + return *this; +#endif // ABSL_HAVE_INTRINSIC128 +} + +// Increment/decrement operators. + +inline uint128 uint128::operator++(int) { + uint128 tmp(*this); + *this += 1; + return tmp; +} + +inline uint128 uint128::operator--(int) { + uint128 tmp(*this); + *this -= 1; + return tmp; +} + +inline uint128& uint128::operator++() { + *this += 1; + return *this; +} + +inline uint128& uint128::operator--() { + *this -= 1; + return *this; +} + +#if defined(ABSL_HAVE_INTRINSIC_INT128) +#include "absl/numeric/int128_have_intrinsic.inc" +#else // ABSL_HAVE_INTRINSIC_INT128 +#include "absl/numeric/int128_no_intrinsic.inc" +#endif // ABSL_HAVE_INTRINSIC_INT128 + +} // namespace absl + +#endif // ABSL_NUMERIC_INT128_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128_have_intrinsic.inc b/Firestore/third_party/abseil-cpp/absl/numeric/int128_have_intrinsic.inc new file mode 100644 index 00000000000..ee2a093018d --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128_have_intrinsic.inc @@ -0,0 +1,18 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains :int128 implementation details that depend on internal +// representation when ABSL_HAVE_INTRINSIC_INT128 is defined. This file is +// included by int128.h. diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128_no_intrinsic.inc b/Firestore/third_party/abseil-cpp/absl/numeric/int128_no_intrinsic.inc new file mode 100644 index 00000000000..0d0b3cfdeb1 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128_no_intrinsic.inc @@ -0,0 +1,18 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains :int128 implementation details that depend on internal +// representation when ABSL_HAVE_INTRINSIC_INT128 is *not* defined. This file +// is included by int128.h. diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128_stream_test.cc b/Firestore/third_party/abseil-cpp/absl/numeric/int128_stream_test.cc new file mode 100644 index 00000000000..09efaad4e7f --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128_stream_test.cc @@ -0,0 +1,666 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/numeric/int128.h" + +#include +#include + +#include "gtest/gtest.h" + +namespace { + +struct Uint128TestCase { + absl::uint128 value; + std::ios_base::fmtflags flags; + std::streamsize width; + const char* expected; +}; + +constexpr char kFill = '_'; + +std::string StreamFormatToString(std::ios_base::fmtflags flags, + std::streamsize width) { + std::vector flagstr; + switch (flags & std::ios::basefield) { + case std::ios::dec: + flagstr.push_back("std::ios::dec"); + break; + case std::ios::oct: + flagstr.push_back("std::ios::oct"); + break; + case std::ios::hex: + flagstr.push_back("std::ios::hex"); + break; + default: // basefield not specified + break; + } + switch (flags & std::ios::adjustfield) { + case std::ios::left: + flagstr.push_back("std::ios::left"); + break; + case std::ios::internal: + flagstr.push_back("std::ios::internal"); + break; + case std::ios::right: + flagstr.push_back("std::ios::right"); + break; + default: // adjustfield not specified + break; + } + if (flags & std::ios::uppercase) flagstr.push_back("std::ios::uppercase"); + if (flags & std::ios::showbase) flagstr.push_back("std::ios::showbase"); + if (flags & std::ios::showpos) flagstr.push_back("std::ios::showpos"); + + std::ostringstream msg; + msg << "\n StreamFormatToString(test_case.flags, test_case.width)\n " + "flags: "; + if (!flagstr.empty()) { + for (size_t i = 0; i < flagstr.size() - 1; ++i) msg << flagstr[i] << " | "; + msg << flagstr.back(); + } else { + msg << "(default)"; + } + msg << "\n width: " << width << "\n fill: '" << kFill << "'"; + return msg.str(); +} + +void CheckUint128Case(const Uint128TestCase& test_case) { + std::ostringstream os; + os.flags(test_case.flags); + os.width(test_case.width); + os.fill(kFill); + os << test_case.value; + SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); + EXPECT_EQ(test_case.expected, os.str()); +} + +constexpr std::ios::fmtflags kDec = std::ios::dec; +constexpr std::ios::fmtflags kOct = std::ios::oct; +constexpr std::ios::fmtflags kHex = std::ios::hex; +constexpr std::ios::fmtflags kLeft = std::ios::left; +constexpr std::ios::fmtflags kInt = std::ios::internal; +constexpr std::ios::fmtflags kRight = std::ios::right; +constexpr std::ios::fmtflags kUpper = std::ios::uppercase; +constexpr std::ios::fmtflags kBase = std::ios::showbase; +constexpr std::ios::fmtflags kPos = std::ios::showpos; + +TEST(Uint128, OStreamValueTest) { + CheckUint128Case({1, kDec, /*width = */ 0, "1"}); + CheckUint128Case({1, kOct, /*width = */ 0, "1"}); + CheckUint128Case({1, kHex, /*width = */ 0, "1"}); + CheckUint128Case({9, kDec, /*width = */ 0, "9"}); + CheckUint128Case({9, kOct, /*width = */ 0, "11"}); + CheckUint128Case({9, kHex, /*width = */ 0, "9"}); + CheckUint128Case({12345, kDec, /*width = */ 0, "12345"}); + CheckUint128Case({12345, kOct, /*width = */ 0, "30071"}); + CheckUint128Case({12345, kHex, /*width = */ 0, "3039"}); + CheckUint128Case( + {0x8000000000000000, kDec, /*width = */ 0, "9223372036854775808"}); + CheckUint128Case( + {0x8000000000000000, kOct, /*width = */ 0, "1000000000000000000000"}); + CheckUint128Case( + {0x8000000000000000, kHex, /*width = */ 0, "8000000000000000"}); + CheckUint128Case({std::numeric_limits::max(), kDec, + /*width = */ 0, "18446744073709551615"}); + CheckUint128Case({std::numeric_limits::max(), kOct, + /*width = */ 0, "1777777777777777777777"}); + CheckUint128Case({std::numeric_limits::max(), kHex, + /*width = */ 0, "ffffffffffffffff"}); + CheckUint128Case( + {absl::MakeUint128(1, 0), kDec, /*width = */ 0, "18446744073709551616"}); + CheckUint128Case({absl::MakeUint128(1, 0), kOct, /*width = */ 0, + "2000000000000000000000"}); + CheckUint128Case( + {absl::MakeUint128(1, 0), kHex, /*width = */ 0, "10000000000000000"}); + CheckUint128Case({absl::MakeUint128(0x8000000000000000, 0), kDec, + /*width = */ 0, "170141183460469231731687303715884105728"}); + CheckUint128Case({absl::MakeUint128(0x8000000000000000, 0), kOct, + /*width = */ 0, + "2000000000000000000000000000000000000000000"}); + CheckUint128Case({absl::MakeUint128(0x8000000000000000, 0), kHex, + /*width = */ 0, "80000000000000000000000000000000"}); + CheckUint128Case({absl::kuint128max, kDec, /*width = */ 0, + "340282366920938463463374607431768211455"}); + CheckUint128Case({absl::kuint128max, kOct, /*width = */ 0, + "3777777777777777777777777777777777777777777"}); + CheckUint128Case({absl::kuint128max, kHex, /*width = */ 0, + "ffffffffffffffffffffffffffffffff"}); +} + +std::vector GetUint128FormatCases(); + +TEST(Uint128, OStreamFormatTest) { + for (const Uint128TestCase& test_case : GetUint128FormatCases()) { + CheckUint128Case(test_case); + } +} + +std::vector GetUint128FormatCases() { + return { + {0, std::ios_base::fmtflags(), /*width = */ 0, "0"}, + {0, std::ios_base::fmtflags(), /*width = */ 6, "_____0"}, + {0, kPos, /*width = */ 0, "0"}, + {0, kPos, /*width = */ 6, "_____0"}, + {0, kBase, /*width = */ 0, "0"}, + {0, kBase, /*width = */ 6, "_____0"}, + {0, kBase | kPos, /*width = */ 0, "0"}, + {0, kBase | kPos, /*width = */ 6, "_____0"}, + {0, kUpper, /*width = */ 0, "0"}, + {0, kUpper, /*width = */ 6, "_____0"}, + {0, kUpper | kPos, /*width = */ 0, "0"}, + {0, kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kUpper | kBase, /*width = */ 0, "0"}, + {0, kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kLeft, /*width = */ 0, "0"}, + {0, kLeft, /*width = */ 6, "0_____"}, + {0, kLeft | kPos, /*width = */ 0, "0"}, + {0, kLeft | kPos, /*width = */ 6, "0_____"}, + {0, kLeft | kBase, /*width = */ 0, "0"}, + {0, kLeft | kBase, /*width = */ 6, "0_____"}, + {0, kLeft | kBase | kPos, /*width = */ 0, "0"}, + {0, kLeft | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kLeft | kUpper, /*width = */ 0, "0"}, + {0, kLeft | kUpper, /*width = */ 6, "0_____"}, + {0, kLeft | kUpper | kPos, /*width = */ 0, "0"}, + {0, kLeft | kUpper | kPos, /*width = */ 6, "0_____"}, + {0, kLeft | kUpper | kBase, /*width = */ 0, "0"}, + {0, kLeft | kUpper | kBase, /*width = */ 6, "0_____"}, + {0, kLeft | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kLeft | kUpper | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kInt, /*width = */ 0, "0"}, + {0, kInt, /*width = */ 6, "_____0"}, + {0, kInt | kPos, /*width = */ 0, "0"}, + {0, kInt | kPos, /*width = */ 6, "_____0"}, + {0, kInt | kBase, /*width = */ 0, "0"}, + {0, kInt | kBase, /*width = */ 6, "_____0"}, + {0, kInt | kBase | kPos, /*width = */ 0, "0"}, + {0, kInt | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kInt | kUpper, /*width = */ 0, "0"}, + {0, kInt | kUpper, /*width = */ 6, "_____0"}, + {0, kInt | kUpper | kPos, /*width = */ 0, "0"}, + {0, kInt | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kInt | kUpper | kBase, /*width = */ 0, "0"}, + {0, kInt | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kInt | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kInt | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kRight, /*width = */ 0, "0"}, + {0, kRight, /*width = */ 6, "_____0"}, + {0, kRight | kPos, /*width = */ 0, "0"}, + {0, kRight | kPos, /*width = */ 6, "_____0"}, + {0, kRight | kBase, /*width = */ 0, "0"}, + {0, kRight | kBase, /*width = */ 6, "_____0"}, + {0, kRight | kBase | kPos, /*width = */ 0, "0"}, + {0, kRight | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kRight | kUpper, /*width = */ 0, "0"}, + {0, kRight | kUpper, /*width = */ 6, "_____0"}, + {0, kRight | kUpper | kPos, /*width = */ 0, "0"}, + {0, kRight | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kRight | kUpper | kBase, /*width = */ 0, "0"}, + {0, kRight | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kRight | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kRight | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kDec, /*width = */ 0, "0"}, + {0, kDec, /*width = */ 6, "_____0"}, + {0, kDec | kPos, /*width = */ 0, "0"}, + {0, kDec | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kBase, /*width = */ 0, "0"}, + {0, kDec | kBase, /*width = */ 6, "_____0"}, + {0, kDec | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kUpper, /*width = */ 0, "0"}, + {0, kDec | kUpper, /*width = */ 6, "_____0"}, + {0, kDec | kUpper | kPos, /*width = */ 0, "0"}, + {0, kDec | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kUpper | kBase, /*width = */ 0, "0"}, + {0, kDec | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kDec | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kLeft, /*width = */ 0, "0"}, + {0, kDec | kLeft, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kPos, /*width = */ 0, "0"}, + {0, kDec | kLeft | kPos, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kBase, /*width = */ 0, "0"}, + {0, kDec | kLeft | kBase, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kLeft | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kUpper, /*width = */ 0, "0"}, + {0, kDec | kLeft | kUpper, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kUpper | kPos, /*width = */ 0, "0"}, + {0, kDec | kLeft | kUpper | kPos, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kUpper | kBase, /*width = */ 0, "0"}, + {0, kDec | kLeft | kUpper | kBase, /*width = */ 6, "0_____"}, + {0, kDec | kLeft | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kLeft | kUpper | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kDec | kInt, /*width = */ 0, "0"}, + {0, kDec | kInt, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kPos, /*width = */ 0, "0"}, + {0, kDec | kInt | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kBase, /*width = */ 0, "0"}, + {0, kDec | kInt | kBase, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kInt | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kUpper, /*width = */ 0, "0"}, + {0, kDec | kInt | kUpper, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kUpper | kPos, /*width = */ 0, "0"}, + {0, kDec | kInt | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kUpper | kBase, /*width = */ 0, "0"}, + {0, kDec | kInt | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kDec | kInt | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kInt | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kRight, /*width = */ 0, "0"}, + {0, kDec | kRight, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kPos, /*width = */ 0, "0"}, + {0, kDec | kRight | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kBase, /*width = */ 0, "0"}, + {0, kDec | kRight | kBase, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kRight | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kUpper, /*width = */ 0, "0"}, + {0, kDec | kRight | kUpper, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kUpper | kPos, /*width = */ 0, "0"}, + {0, kDec | kRight | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kUpper | kBase, /*width = */ 0, "0"}, + {0, kDec | kRight | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kDec | kRight | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kDec | kRight | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kOct, /*width = */ 0, "0"}, + {0, kOct, /*width = */ 6, "_____0"}, + {0, kOct | kPos, /*width = */ 0, "0"}, + {0, kOct | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kBase, /*width = */ 0, "0"}, + {0, kOct | kBase, /*width = */ 6, "_____0"}, + {0, kOct | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kUpper, /*width = */ 0, "0"}, + {0, kOct | kUpper, /*width = */ 6, "_____0"}, + {0, kOct | kUpper | kPos, /*width = */ 0, "0"}, + {0, kOct | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kUpper | kBase, /*width = */ 0, "0"}, + {0, kOct | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kOct | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kLeft, /*width = */ 0, "0"}, + {0, kOct | kLeft, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kPos, /*width = */ 0, "0"}, + {0, kOct | kLeft | kPos, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kBase, /*width = */ 0, "0"}, + {0, kOct | kLeft | kBase, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kLeft | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kUpper, /*width = */ 0, "0"}, + {0, kOct | kLeft | kUpper, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kUpper | kPos, /*width = */ 0, "0"}, + {0, kOct | kLeft | kUpper | kPos, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kUpper | kBase, /*width = */ 0, "0"}, + {0, kOct | kLeft | kUpper | kBase, /*width = */ 6, "0_____"}, + {0, kOct | kLeft | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kLeft | kUpper | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kOct | kInt, /*width = */ 0, "0"}, + {0, kOct | kInt, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kPos, /*width = */ 0, "0"}, + {0, kOct | kInt | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kBase, /*width = */ 0, "0"}, + {0, kOct | kInt | kBase, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kInt | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kUpper, /*width = */ 0, "0"}, + {0, kOct | kInt | kUpper, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kUpper | kPos, /*width = */ 0, "0"}, + {0, kOct | kInt | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kUpper | kBase, /*width = */ 0, "0"}, + {0, kOct | kInt | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kOct | kInt | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kInt | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kRight, /*width = */ 0, "0"}, + {0, kOct | kRight, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kPos, /*width = */ 0, "0"}, + {0, kOct | kRight | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kBase, /*width = */ 0, "0"}, + {0, kOct | kRight | kBase, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kRight | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kUpper, /*width = */ 0, "0"}, + {0, kOct | kRight | kUpper, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kUpper | kPos, /*width = */ 0, "0"}, + {0, kOct | kRight | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kUpper | kBase, /*width = */ 0, "0"}, + {0, kOct | kRight | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kOct | kRight | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kOct | kRight | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kHex, /*width = */ 0, "0"}, + {0, kHex, /*width = */ 6, "_____0"}, + {0, kHex | kPos, /*width = */ 0, "0"}, + {0, kHex | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kBase, /*width = */ 0, "0"}, + {0, kHex | kBase, /*width = */ 6, "_____0"}, + {0, kHex | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kUpper, /*width = */ 0, "0"}, + {0, kHex | kUpper, /*width = */ 6, "_____0"}, + {0, kHex | kUpper | kPos, /*width = */ 0, "0"}, + {0, kHex | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kUpper | kBase, /*width = */ 0, "0"}, + {0, kHex | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kHex | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kLeft, /*width = */ 0, "0"}, + {0, kHex | kLeft, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kPos, /*width = */ 0, "0"}, + {0, kHex | kLeft | kPos, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kBase, /*width = */ 0, "0"}, + {0, kHex | kLeft | kBase, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kLeft | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kUpper, /*width = */ 0, "0"}, + {0, kHex | kLeft | kUpper, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kUpper | kPos, /*width = */ 0, "0"}, + {0, kHex | kLeft | kUpper | kPos, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kUpper | kBase, /*width = */ 0, "0"}, + {0, kHex | kLeft | kUpper | kBase, /*width = */ 6, "0_____"}, + {0, kHex | kLeft | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kLeft | kUpper | kBase | kPos, /*width = */ 6, "0_____"}, + {0, kHex | kInt, /*width = */ 0, "0"}, + {0, kHex | kInt, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kPos, /*width = */ 0, "0"}, + {0, kHex | kInt | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kBase, /*width = */ 0, "0"}, + {0, kHex | kInt | kBase, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kInt | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kUpper, /*width = */ 0, "0"}, + {0, kHex | kInt | kUpper, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kUpper | kPos, /*width = */ 0, "0"}, + {0, kHex | kInt | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kUpper | kBase, /*width = */ 0, "0"}, + {0, kHex | kInt | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kHex | kInt | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kInt | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kRight, /*width = */ 0, "0"}, + {0, kHex | kRight, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kPos, /*width = */ 0, "0"}, + {0, kHex | kRight | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kBase, /*width = */ 0, "0"}, + {0, kHex | kRight | kBase, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kRight | kBase | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kUpper, /*width = */ 0, "0"}, + {0, kHex | kRight | kUpper, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kUpper | kPos, /*width = */ 0, "0"}, + {0, kHex | kRight | kUpper | kPos, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kUpper | kBase, /*width = */ 0, "0"}, + {0, kHex | kRight | kUpper | kBase, /*width = */ 6, "_____0"}, + {0, kHex | kRight | kUpper | kBase | kPos, /*width = */ 0, "0"}, + {0, kHex | kRight | kUpper | kBase | kPos, /*width = */ 6, "_____0"}, + {37, std::ios_base::fmtflags(), /*width = */ 0, "37"}, + {37, std::ios_base::fmtflags(), /*width = */ 6, "____37"}, + {37, kPos, /*width = */ 0, "37"}, + {37, kPos, /*width = */ 6, "____37"}, + {37, kBase, /*width = */ 0, "37"}, + {37, kBase, /*width = */ 6, "____37"}, + {37, kBase | kPos, /*width = */ 0, "37"}, + {37, kBase | kPos, /*width = */ 6, "____37"}, + {37, kUpper, /*width = */ 0, "37"}, + {37, kUpper, /*width = */ 6, "____37"}, + {37, kUpper | kPos, /*width = */ 0, "37"}, + {37, kUpper | kPos, /*width = */ 6, "____37"}, + {37, kUpper | kBase, /*width = */ 0, "37"}, + {37, kUpper | kBase, /*width = */ 6, "____37"}, + {37, kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kUpper | kBase | kPos, /*width = */ 6, "____37"}, + {37, kLeft, /*width = */ 0, "37"}, + {37, kLeft, /*width = */ 6, "37____"}, + {37, kLeft | kPos, /*width = */ 0, "37"}, + {37, kLeft | kPos, /*width = */ 6, "37____"}, + {37, kLeft | kBase, /*width = */ 0, "37"}, + {37, kLeft | kBase, /*width = */ 6, "37____"}, + {37, kLeft | kBase | kPos, /*width = */ 0, "37"}, + {37, kLeft | kBase | kPos, /*width = */ 6, "37____"}, + {37, kLeft | kUpper, /*width = */ 0, "37"}, + {37, kLeft | kUpper, /*width = */ 6, "37____"}, + {37, kLeft | kUpper | kPos, /*width = */ 0, "37"}, + {37, kLeft | kUpper | kPos, /*width = */ 6, "37____"}, + {37, kLeft | kUpper | kBase, /*width = */ 0, "37"}, + {37, kLeft | kUpper | kBase, /*width = */ 6, "37____"}, + {37, kLeft | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kLeft | kUpper | kBase | kPos, /*width = */ 6, "37____"}, + {37, kInt, /*width = */ 0, "37"}, + {37, kInt, /*width = */ 6, "____37"}, + {37, kInt | kPos, /*width = */ 0, "37"}, + {37, kInt | kPos, /*width = */ 6, "____37"}, + {37, kInt | kBase, /*width = */ 0, "37"}, + {37, kInt | kBase, /*width = */ 6, "____37"}, + {37, kInt | kBase | kPos, /*width = */ 0, "37"}, + {37, kInt | kBase | kPos, /*width = */ 6, "____37"}, + {37, kInt | kUpper, /*width = */ 0, "37"}, + {37, kInt | kUpper, /*width = */ 6, "____37"}, + {37, kInt | kUpper | kPos, /*width = */ 0, "37"}, + {37, kInt | kUpper | kPos, /*width = */ 6, "____37"}, + {37, kInt | kUpper | kBase, /*width = */ 0, "37"}, + {37, kInt | kUpper | kBase, /*width = */ 6, "____37"}, + {37, kInt | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kInt | kUpper | kBase | kPos, /*width = */ 6, "____37"}, + {37, kRight, /*width = */ 0, "37"}, + {37, kRight, /*width = */ 6, "____37"}, + {37, kRight | kPos, /*width = */ 0, "37"}, + {37, kRight | kPos, /*width = */ 6, "____37"}, + {37, kRight | kBase, /*width = */ 0, "37"}, + {37, kRight | kBase, /*width = */ 6, "____37"}, + {37, kRight | kBase | kPos, /*width = */ 0, "37"}, + {37, kRight | kBase | kPos, /*width = */ 6, "____37"}, + {37, kRight | kUpper, /*width = */ 0, "37"}, + {37, kRight | kUpper, /*width = */ 6, "____37"}, + {37, kRight | kUpper | kPos, /*width = */ 0, "37"}, + {37, kRight | kUpper | kPos, /*width = */ 6, "____37"}, + {37, kRight | kUpper | kBase, /*width = */ 0, "37"}, + {37, kRight | kUpper | kBase, /*width = */ 6, "____37"}, + {37, kRight | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kRight | kUpper | kBase | kPos, /*width = */ 6, "____37"}, + {37, kDec, /*width = */ 0, "37"}, + {37, kDec, /*width = */ 6, "____37"}, + {37, kDec | kPos, /*width = */ 0, "37"}, + {37, kDec | kPos, /*width = */ 6, "____37"}, + {37, kDec | kBase, /*width = */ 0, "37"}, + {37, kDec | kBase, /*width = */ 6, "____37"}, + {37, kDec | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kBase | kPos, /*width = */ 6, "____37"}, + {37, kDec | kUpper, /*width = */ 0, "37"}, + {37, kDec | kUpper, /*width = */ 6, "____37"}, + {37, kDec | kUpper | kPos, /*width = */ 0, "37"}, + {37, kDec | kUpper | kPos, /*width = */ 6, "____37"}, + {37, kDec | kUpper | kBase, /*width = */ 0, "37"}, + {37, kDec | kUpper | kBase, /*width = */ 6, "____37"}, + {37, kDec | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kUpper | kBase | kPos, /*width = */ 6, "____37"}, + {37, kDec | kLeft, /*width = */ 0, "37"}, + {37, kDec | kLeft, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kPos, /*width = */ 0, "37"}, + {37, kDec | kLeft | kPos, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kBase, /*width = */ 0, "37"}, + {37, kDec | kLeft | kBase, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kLeft | kBase | kPos, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kUpper, /*width = */ 0, "37"}, + {37, kDec | kLeft | kUpper, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kUpper | kPos, /*width = */ 0, "37"}, + {37, kDec | kLeft | kUpper | kPos, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kUpper | kBase, /*width = */ 0, "37"}, + {37, kDec | kLeft | kUpper | kBase, /*width = */ 6, "37____"}, + {37, kDec | kLeft | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kLeft | kUpper | kBase | kPos, /*width = */ 6, "37____"}, + {37, kDec | kInt, /*width = */ 0, "37"}, + {37, kDec | kInt, /*width = */ 6, "____37"}, + {37, kDec | kInt | kPos, /*width = */ 0, "37"}, + {37, kDec | kInt | kPos, /*width = */ 6, "____37"}, + {37, kDec | kInt | kBase, /*width = */ 0, "37"}, + {37, kDec | kInt | kBase, /*width = */ 6, "____37"}, + {37, kDec | kInt | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kInt | kBase | kPos, /*width = */ 6, "____37"}, + {37, kDec | kInt | kUpper, /*width = */ 0, "37"}, + {37, kDec | kInt | kUpper, /*width = */ 6, "____37"}, + {37, kDec | kInt | kUpper | kPos, /*width = */ 0, "37"}, + {37, kDec | kInt | kUpper | kPos, /*width = */ 6, "____37"}, + {37, kDec | kInt | kUpper | kBase, /*width = */ 0, "37"}, + {37, kDec | kInt | kUpper | kBase, /*width = */ 6, "____37"}, + {37, kDec | kInt | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kInt | kUpper | kBase | kPos, /*width = */ 6, "____37"}, + {37, kDec | kRight, /*width = */ 0, "37"}, + {37, kDec | kRight, /*width = */ 6, "____37"}, + {37, kDec | kRight | kPos, /*width = */ 0, "37"}, + {37, kDec | kRight | kPos, /*width = */ 6, "____37"}, + {37, kDec | kRight | kBase, /*width = */ 0, "37"}, + {37, kDec | kRight | kBase, /*width = */ 6, "____37"}, + {37, kDec | kRight | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kRight | kBase | kPos, /*width = */ 6, "____37"}, + {37, kDec | kRight | kUpper, /*width = */ 0, "37"}, + {37, kDec | kRight | kUpper, /*width = */ 6, "____37"}, + {37, kDec | kRight | kUpper | kPos, /*width = */ 0, "37"}, + {37, kDec | kRight | kUpper | kPos, /*width = */ 6, "____37"}, + {37, kDec | kRight | kUpper | kBase, /*width = */ 0, "37"}, + {37, kDec | kRight | kUpper | kBase, /*width = */ 6, "____37"}, + {37, kDec | kRight | kUpper | kBase | kPos, /*width = */ 0, "37"}, + {37, kDec | kRight | kUpper | kBase | kPos, /*width = */ 6, "____37"}, + {37, kOct, /*width = */ 0, "45"}, + {37, kOct, /*width = */ 6, "____45"}, + {37, kOct | kPos, /*width = */ 0, "45"}, + {37, kOct | kPos, /*width = */ 6, "____45"}, + {37, kOct | kBase, /*width = */ 0, "045"}, + {37, kOct | kBase, /*width = */ 6, "___045"}, + {37, kOct | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kBase | kPos, /*width = */ 6, "___045"}, + {37, kOct | kUpper, /*width = */ 0, "45"}, + {37, kOct | kUpper, /*width = */ 6, "____45"}, + {37, kOct | kUpper | kPos, /*width = */ 0, "45"}, + {37, kOct | kUpper | kPos, /*width = */ 6, "____45"}, + {37, kOct | kUpper | kBase, /*width = */ 0, "045"}, + {37, kOct | kUpper | kBase, /*width = */ 6, "___045"}, + {37, kOct | kUpper | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kUpper | kBase | kPos, /*width = */ 6, "___045"}, + {37, kOct | kLeft, /*width = */ 0, "45"}, + {37, kOct | kLeft, /*width = */ 6, "45____"}, + {37, kOct | kLeft | kPos, /*width = */ 0, "45"}, + {37, kOct | kLeft | kPos, /*width = */ 6, "45____"}, + {37, kOct | kLeft | kBase, /*width = */ 0, "045"}, + {37, kOct | kLeft | kBase, /*width = */ 6, "045___"}, + {37, kOct | kLeft | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kLeft | kBase | kPos, /*width = */ 6, "045___"}, + {37, kOct | kLeft | kUpper, /*width = */ 0, "45"}, + {37, kOct | kLeft | kUpper, /*width = */ 6, "45____"}, + {37, kOct | kLeft | kUpper | kPos, /*width = */ 0, "45"}, + {37, kOct | kLeft | kUpper | kPos, /*width = */ 6, "45____"}, + {37, kOct | kLeft | kUpper | kBase, /*width = */ 0, "045"}, + {37, kOct | kLeft | kUpper | kBase, /*width = */ 6, "045___"}, + {37, kOct | kLeft | kUpper | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kLeft | kUpper | kBase | kPos, /*width = */ 6, "045___"}, + {37, kOct | kInt, /*width = */ 0, "45"}, + {37, kOct | kInt, /*width = */ 6, "____45"}, + {37, kOct | kInt | kPos, /*width = */ 0, "45"}, + {37, kOct | kInt | kPos, /*width = */ 6, "____45"}, + {37, kOct | kInt | kBase, /*width = */ 0, "045"}, + {37, kOct | kInt | kBase, /*width = */ 6, "___045"}, + {37, kOct | kInt | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kInt | kBase | kPos, /*width = */ 6, "___045"}, + {37, kOct | kInt | kUpper, /*width = */ 0, "45"}, + {37, kOct | kInt | kUpper, /*width = */ 6, "____45"}, + {37, kOct | kInt | kUpper | kPos, /*width = */ 0, "45"}, + {37, kOct | kInt | kUpper | kPos, /*width = */ 6, "____45"}, + {37, kOct | kInt | kUpper | kBase, /*width = */ 0, "045"}, + {37, kOct | kInt | kUpper | kBase, /*width = */ 6, "___045"}, + {37, kOct | kInt | kUpper | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kInt | kUpper | kBase | kPos, /*width = */ 6, "___045"}, + {37, kOct | kRight, /*width = */ 0, "45"}, + {37, kOct | kRight, /*width = */ 6, "____45"}, + {37, kOct | kRight | kPos, /*width = */ 0, "45"}, + {37, kOct | kRight | kPos, /*width = */ 6, "____45"}, + {37, kOct | kRight | kBase, /*width = */ 0, "045"}, + {37, kOct | kRight | kBase, /*width = */ 6, "___045"}, + {37, kOct | kRight | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kRight | kBase | kPos, /*width = */ 6, "___045"}, + {37, kOct | kRight | kUpper, /*width = */ 0, "45"}, + {37, kOct | kRight | kUpper, /*width = */ 6, "____45"}, + {37, kOct | kRight | kUpper | kPos, /*width = */ 0, "45"}, + {37, kOct | kRight | kUpper | kPos, /*width = */ 6, "____45"}, + {37, kOct | kRight | kUpper | kBase, /*width = */ 0, "045"}, + {37, kOct | kRight | kUpper | kBase, /*width = */ 6, "___045"}, + {37, kOct | kRight | kUpper | kBase | kPos, /*width = */ 0, "045"}, + {37, kOct | kRight | kUpper | kBase | kPos, /*width = */ 6, "___045"}, + {37, kHex, /*width = */ 0, "25"}, + {37, kHex, /*width = */ 6, "____25"}, + {37, kHex | kPos, /*width = */ 0, "25"}, + {37, kHex | kPos, /*width = */ 6, "____25"}, + {37, kHex | kBase, /*width = */ 0, "0x25"}, + {37, kHex | kBase, /*width = */ 6, "__0x25"}, + {37, kHex | kBase | kPos, /*width = */ 0, "0x25"}, + {37, kHex | kBase | kPos, /*width = */ 6, "__0x25"}, + {37, kHex | kUpper, /*width = */ 0, "25"}, + {37, kHex | kUpper, /*width = */ 6, "____25"}, + {37, kHex | kUpper | kPos, /*width = */ 0, "25"}, + {37, kHex | kUpper | kPos, /*width = */ 6, "____25"}, + {37, kHex | kUpper | kBase, /*width = */ 0, "0X25"}, + {37, kHex | kUpper | kBase, /*width = */ 6, "__0X25"}, + {37, kHex | kUpper | kBase | kPos, /*width = */ 0, "0X25"}, + {37, kHex | kUpper | kBase | kPos, /*width = */ 6, "__0X25"}, + {37, kHex | kLeft, /*width = */ 0, "25"}, + {37, kHex | kLeft, /*width = */ 6, "25____"}, + {37, kHex | kLeft | kPos, /*width = */ 0, "25"}, + {37, kHex | kLeft | kPos, /*width = */ 6, "25____"}, + {37, kHex | kLeft | kBase, /*width = */ 0, "0x25"}, + {37, kHex | kLeft | kBase, /*width = */ 6, "0x25__"}, + {37, kHex | kLeft | kBase | kPos, /*width = */ 0, "0x25"}, + {37, kHex | kLeft | kBase | kPos, /*width = */ 6, "0x25__"}, + {37, kHex | kLeft | kUpper, /*width = */ 0, "25"}, + {37, kHex | kLeft | kUpper, /*width = */ 6, "25____"}, + {37, kHex | kLeft | kUpper | kPos, /*width = */ 0, "25"}, + {37, kHex | kLeft | kUpper | kPos, /*width = */ 6, "25____"}, + {37, kHex | kLeft | kUpper | kBase, /*width = */ 0, "0X25"}, + {37, kHex | kLeft | kUpper | kBase, /*width = */ 6, "0X25__"}, + {37, kHex | kLeft | kUpper | kBase | kPos, /*width = */ 0, "0X25"}, + {37, kHex | kLeft | kUpper | kBase | kPos, /*width = */ 6, "0X25__"}, + {37, kHex | kInt, /*width = */ 0, "25"}, + {37, kHex | kInt, /*width = */ 6, "____25"}, + {37, kHex | kInt | kPos, /*width = */ 0, "25"}, + {37, kHex | kInt | kPos, /*width = */ 6, "____25"}, + {37, kHex | kInt | kBase, /*width = */ 0, "0x25"}, + {37, kHex | kInt | kBase, /*width = */ 6, "0x__25"}, + {37, kHex | kInt | kBase | kPos, /*width = */ 0, "0x25"}, + {37, kHex | kInt | kBase | kPos, /*width = */ 6, "0x__25"}, + {37, kHex | kInt | kUpper, /*width = */ 0, "25"}, + {37, kHex | kInt | kUpper, /*width = */ 6, "____25"}, + {37, kHex | kInt | kUpper | kPos, /*width = */ 0, "25"}, + {37, kHex | kInt | kUpper | kPos, /*width = */ 6, "____25"}, + {37, kHex | kInt | kUpper | kBase, /*width = */ 0, "0X25"}, + {37, kHex | kInt | kUpper | kBase, /*width = */ 6, "0X__25"}, + {37, kHex | kInt | kUpper | kBase | kPos, /*width = */ 0, "0X25"}, + {37, kHex | kInt | kUpper | kBase | kPos, /*width = */ 6, "0X__25"}, + {37, kHex | kRight, /*width = */ 0, "25"}, + {37, kHex | kRight, /*width = */ 6, "____25"}, + {37, kHex | kRight | kPos, /*width = */ 0, "25"}, + {37, kHex | kRight | kPos, /*width = */ 6, "____25"}, + {37, kHex | kRight | kBase, /*width = */ 0, "0x25"}, + {37, kHex | kRight | kBase, /*width = */ 6, "__0x25"}, + {37, kHex | kRight | kBase | kPos, /*width = */ 0, "0x25"}, + {37, kHex | kRight | kBase | kPos, /*width = */ 6, "__0x25"}, + {37, kHex | kRight | kUpper, /*width = */ 0, "25"}, + {37, kHex | kRight | kUpper, /*width = */ 6, "____25"}, + {37, kHex | kRight | kUpper | kPos, /*width = */ 0, "25"}, + {37, kHex | kRight | kUpper | kPos, /*width = */ 6, "____25"}, + {37, kHex | kRight | kUpper | kBase, /*width = */ 0, "0X25"}, + {37, kHex | kRight | kUpper | kBase, /*width = */ 6, "__0X25"}, + {37, kHex | kRight | kUpper | kBase | kPos, /*width = */ 0, "0X25"}, + {37, kHex | kRight | kUpper | kBase | kPos, /*width = */ 6, "__0X25"}}; +} + +} // namespace diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc b/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc new file mode 100644 index 00000000000..79bcca907ae --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/numeric/int128_test.cc @@ -0,0 +1,431 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/numeric/int128.h" + +#include +#include +#include +#include +#include +#include + +#include "gtest/gtest.h" +#include "absl/base/internal/cycleclock.h" +#include "absl/meta/type_traits.h" + +#if defined(_MSC_VER) && _MSC_VER == 1900 +// Disable "unary minus operator applied to unsigned type" warnings in Microsoft +// Visual C++ 14 (2015). +#pragma warning(disable:4146) +#endif + +namespace { + +template +class Uint128IntegerTraitsTest : public ::testing::Test {}; +typedef ::testing::Types // NOLINT(runtime/int) + IntegerTypes; + +template +class Uint128FloatTraitsTest : public ::testing::Test {}; +typedef ::testing::Types FloatingPointTypes; + +TYPED_TEST_CASE(Uint128IntegerTraitsTest, IntegerTypes); + +TYPED_TEST(Uint128IntegerTraitsTest, ConstructAssignTest) { + static_assert(std::is_constructible::value, + "absl::uint128 must be constructible from TypeParam"); + static_assert(std::is_assignable::value, + "absl::uint128 must be assignable from TypeParam"); + static_assert(!std::is_assignable::value, + "TypeParam must not be assignable from absl::uint128"); +} + +TYPED_TEST_CASE(Uint128FloatTraitsTest, FloatingPointTypes); + +TYPED_TEST(Uint128FloatTraitsTest, ConstructAssignTest) { + static_assert(std::is_constructible::value, + "absl::uint128 must be constructible from TypeParam"); + static_assert(!std::is_assignable::value, + "absl::uint128 must not be assignable from TypeParam"); + static_assert(!std::is_assignable::value, + "TypeParam must not be assignable from absl::uint128"); +} + +#ifdef ABSL_HAVE_INTRINSIC_INT128 +// These type traits done separately as TYPED_TEST requires typeinfo, and not +// all platforms have this for __int128 even though they define the type. +TEST(Uint128, IntrinsicTypeTraitsTest) { + static_assert(std::is_constructible::value, + "absl::uint128 must be constructible from __int128"); + static_assert(std::is_assignable::value, + "absl::uint128 must be assignable from __int128"); + static_assert(!std::is_assignable<__int128&, absl::uint128>::value, + "__int128 must not be assignable from absl::uint128"); + + static_assert(std::is_constructible::value, + "absl::uint128 must be constructible from unsigned __int128"); + static_assert(std::is_assignable::value, + "absl::uint128 must be assignable from unsigned __int128"); + static_assert(!std::is_assignable::value, + "unsigned __int128 must not be assignable from absl::uint128"); +} +#endif // ABSL_HAVE_INTRINSIC_INT128 + +TEST(Uint128, TrivialTraitsTest) { + static_assert(absl::is_trivially_default_constructible::value, + ""); + static_assert(absl::is_trivially_copy_constructible::value, + ""); + static_assert(absl::is_trivially_copy_assignable::value, ""); + static_assert(std::is_trivially_destructible::value, ""); +} + +TEST(Uint128, AllTests) { + absl::uint128 zero = 0; + absl::uint128 one = 1; + absl::uint128 one_2arg = absl::MakeUint128(0, 1); + absl::uint128 two = 2; + absl::uint128 three = 3; + absl::uint128 big = absl::MakeUint128(2000, 2); + absl::uint128 big_minus_one = absl::MakeUint128(2000, 1); + absl::uint128 bigger = absl::MakeUint128(2001, 1); + absl::uint128 biggest = absl::Uint128Max(); + absl::uint128 high_low = absl::MakeUint128(1, 0); + absl::uint128 low_high = + absl::MakeUint128(0, std::numeric_limits::max()); + EXPECT_LT(one, two); + EXPECT_GT(two, one); + EXPECT_LT(one, big); + EXPECT_LT(one, big); + EXPECT_EQ(one, one_2arg); + EXPECT_NE(one, two); + EXPECT_GT(big, one); + EXPECT_GE(big, two); + EXPECT_GE(big, big_minus_one); + EXPECT_GT(big, big_minus_one); + EXPECT_LT(big_minus_one, big); + EXPECT_LE(big_minus_one, big); + EXPECT_NE(big_minus_one, big); + EXPECT_LT(big, biggest); + EXPECT_LE(big, biggest); + EXPECT_GT(biggest, big); + EXPECT_GE(biggest, big); + EXPECT_EQ(big, ~~big); + EXPECT_EQ(one, one | one); + EXPECT_EQ(big, big | big); + EXPECT_EQ(one, one | zero); + EXPECT_EQ(one, one & one); + EXPECT_EQ(big, big & big); + EXPECT_EQ(zero, one & zero); + EXPECT_EQ(zero, big & ~big); + EXPECT_EQ(zero, one ^ one); + EXPECT_EQ(zero, big ^ big); + EXPECT_EQ(one, one ^ zero); + + // Shift operators. + EXPECT_EQ(big, big << 0); + EXPECT_EQ(big, big >> 0); + EXPECT_GT(big << 1, big); + EXPECT_LT(big >> 1, big); + EXPECT_EQ(big, (big << 10) >> 10); + EXPECT_EQ(big, (big >> 1) << 1); + EXPECT_EQ(one, (one << 80) >> 80); + EXPECT_EQ(zero, (one >> 80) << 80); + + // Shift assignments. + absl::uint128 big_copy = big; + EXPECT_EQ(big << 0, big_copy <<= 0); + big_copy = big; + EXPECT_EQ(big >> 0, big_copy >>= 0); + big_copy = big; + EXPECT_EQ(big << 1, big_copy <<= 1); + big_copy = big; + EXPECT_EQ(big >> 1, big_copy >>= 1); + big_copy = big; + EXPECT_EQ(big << 10, big_copy <<= 10); + big_copy = big; + EXPECT_EQ(big >> 10, big_copy >>= 10); + big_copy = big; + EXPECT_EQ(big << 64, big_copy <<= 64); + big_copy = big; + EXPECT_EQ(big >> 64, big_copy >>= 64); + big_copy = big; + EXPECT_EQ(big << 73, big_copy <<= 73); + big_copy = big; + EXPECT_EQ(big >> 73, big_copy >>= 73); + + EXPECT_EQ(absl::Uint128High64(biggest), std::numeric_limits::max()); + EXPECT_EQ(absl::Uint128Low64(biggest), std::numeric_limits::max()); + EXPECT_EQ(zero + one, one); + EXPECT_EQ(one + one, two); + EXPECT_EQ(big_minus_one + one, big); + EXPECT_EQ(one - one, zero); + EXPECT_EQ(one - zero, one); + EXPECT_EQ(zero - one, biggest); + EXPECT_EQ(big - big, zero); + EXPECT_EQ(big - one, big_minus_one); + EXPECT_EQ(big + std::numeric_limits::max(), bigger); + EXPECT_EQ(biggest + 1, zero); + EXPECT_EQ(zero - 1, biggest); + EXPECT_EQ(high_low - one, low_high); + EXPECT_EQ(low_high + one, high_low); + EXPECT_EQ(absl::Uint128High64((absl::uint128(1) << 64) - 1), 0); + EXPECT_EQ(absl::Uint128Low64((absl::uint128(1) << 64) - 1), + std::numeric_limits::max()); + EXPECT_TRUE(!!one); + EXPECT_TRUE(!!high_low); + EXPECT_FALSE(!!zero); + EXPECT_FALSE(!one); + EXPECT_FALSE(!high_low); + EXPECT_TRUE(!zero); + EXPECT_TRUE(zero == 0); // NOLINT(readability/check) + EXPECT_FALSE(zero != 0); // NOLINT(readability/check) + EXPECT_FALSE(one == 0); // NOLINT(readability/check) + EXPECT_TRUE(one != 0); // NOLINT(readability/check) + EXPECT_FALSE(high_low == 0); // NOLINT(readability/check) + EXPECT_TRUE(high_low != 0); // NOLINT(readability/check) + + absl::uint128 test = zero; + EXPECT_EQ(++test, one); + EXPECT_EQ(test, one); + EXPECT_EQ(test++, one); + EXPECT_EQ(test, two); + EXPECT_EQ(test -= 2, zero); + EXPECT_EQ(test, zero); + EXPECT_EQ(test += 2, two); + EXPECT_EQ(test, two); + EXPECT_EQ(--test, one); + EXPECT_EQ(test, one); + EXPECT_EQ(test--, one); + EXPECT_EQ(test, zero); + EXPECT_EQ(test |= three, three); + EXPECT_EQ(test &= one, one); + EXPECT_EQ(test ^= three, two); + EXPECT_EQ(test >>= 1, one); + EXPECT_EQ(test <<= 1, two); + + EXPECT_EQ(big, -(-big)); + EXPECT_EQ(two, -((-one) - 1)); + EXPECT_EQ(absl::Uint128Max(), -one); + EXPECT_EQ(zero, -zero); + + EXPECT_EQ(absl::Uint128Max(), absl::kuint128max); +} + +TEST(Uint128, ConversionTests) { + EXPECT_TRUE(absl::MakeUint128(1, 0)); + +#ifdef ABSL_HAVE_INTRINSIC_INT128 + unsigned __int128 intrinsic = + (static_cast(0x3a5b76c209de76f6) << 64) + + 0x1f25e1d63a2b46c5; + absl::uint128 custom = + absl::MakeUint128(0x3a5b76c209de76f6, 0x1f25e1d63a2b46c5); + + EXPECT_EQ(custom, absl::uint128(intrinsic)); + EXPECT_EQ(custom, absl::uint128(static_cast<__int128>(intrinsic))); + EXPECT_EQ(intrinsic, static_cast(custom)); + EXPECT_EQ(intrinsic, static_cast<__int128>(custom)); +#endif // ABSL_HAVE_INTRINSIC_INT128 + + // verify that an integer greater than 2**64 that can be stored precisely + // inside a double is converted to a absl::uint128 without loss of + // information. + double precise_double = 0x530e * std::pow(2.0, 64.0) + 0xda74000000000000; + absl::uint128 from_precise_double(precise_double); + absl::uint128 from_precise_ints = + absl::MakeUint128(0x530e, 0xda74000000000000); + EXPECT_EQ(from_precise_double, from_precise_ints); + EXPECT_DOUBLE_EQ(static_cast(from_precise_ints), precise_double); + + double approx_double = 0xffffeeeeddddcccc * std::pow(2.0, 64.0) + + 0xbbbbaaaa99998888; + absl::uint128 from_approx_double(approx_double); + EXPECT_DOUBLE_EQ(static_cast(from_approx_double), approx_double); + + double round_to_zero = 0.7; + double round_to_five = 5.8; + double round_to_nine = 9.3; + EXPECT_EQ(static_cast(round_to_zero), 0); + EXPECT_EQ(static_cast(round_to_five), 5); + EXPECT_EQ(static_cast(round_to_nine), 9); +} + +TEST(Uint128, OperatorAssignReturnRef) { + absl::uint128 v(1); + (v += 4) -= 3; + EXPECT_EQ(2, v); +} + +TEST(Uint128, Multiply) { + absl::uint128 a, b, c; + + // Zero test. + a = 0; + b = 0; + c = a * b; + EXPECT_EQ(0, c); + + // Max carries. + a = absl::uint128(0) - 1; + b = absl::uint128(0) - 1; + c = a * b; + EXPECT_EQ(1, c); + + // Self-operation with max carries. + c = absl::uint128(0) - 1; + c *= c; + EXPECT_EQ(1, c); + + // 1-bit x 1-bit. + for (int i = 0; i < 64; ++i) { + for (int j = 0; j < 64; ++j) { + a = absl::uint128(1) << i; + b = absl::uint128(1) << j; + c = a * b; + EXPECT_EQ(absl::uint128(1) << (i + j), c); + } + } + + // Verified with dc. + a = absl::MakeUint128(0xffffeeeeddddcccc, 0xbbbbaaaa99998888); + b = absl::MakeUint128(0x7777666655554444, 0x3333222211110000); + c = a * b; + EXPECT_EQ(absl::MakeUint128(0x530EDA741C71D4C3, 0xBF25975319080000), c); + EXPECT_EQ(0, c - b * a); + EXPECT_EQ(a*a - b*b, (a+b) * (a-b)); + + // Verified with dc. + a = absl::MakeUint128(0x0123456789abcdef, 0xfedcba9876543210); + b = absl::MakeUint128(0x02468ace13579bdf, 0xfdb97531eca86420); + c = a * b; + EXPECT_EQ(absl::MakeUint128(0x97a87f4f261ba3f2, 0x342d0bbf48948200), c); + EXPECT_EQ(0, c - b * a); + EXPECT_EQ(a*a - b*b, (a+b) * (a-b)); +} + +TEST(Uint128, AliasTests) { + absl::uint128 x1 = absl::MakeUint128(1, 2); + absl::uint128 x2 = absl::MakeUint128(2, 4); + x1 += x1; + EXPECT_EQ(x2, x1); + + absl::uint128 x3 = absl::MakeUint128(1, static_cast(1) << 63); + absl::uint128 x4 = absl::MakeUint128(3, 0); + x3 += x3; + EXPECT_EQ(x4, x3); +} + +TEST(Uint128, DivideAndMod) { + using std::swap; + + // a := q * b + r + absl::uint128 a, b, q, r; + + // Zero test. + a = 0; + b = 123; + q = a / b; + r = a % b; + EXPECT_EQ(0, q); + EXPECT_EQ(0, r); + + a = absl::MakeUint128(0x530eda741c71d4c3, 0xbf25975319080000); + q = absl::MakeUint128(0x4de2cab081, 0x14c34ab4676e4bab); + b = absl::uint128(0x1110001); + r = absl::uint128(0x3eb455); + ASSERT_EQ(a, q * b + r); // Sanity-check. + + absl::uint128 result_q, result_r; + result_q = a / b; + result_r = a % b; + EXPECT_EQ(q, result_q); + EXPECT_EQ(r, result_r); + + // Try the other way around. + swap(q, b); + result_q = a / b; + result_r = a % b; + EXPECT_EQ(q, result_q); + EXPECT_EQ(r, result_r); + // Restore. + swap(b, q); + + // Dividend < divisor; result should be q:0 r:. + swap(a, b); + result_q = a / b; + result_r = a % b; + EXPECT_EQ(0, result_q); + EXPECT_EQ(a, result_r); + // Try the other way around. + swap(a, q); + result_q = a / b; + result_r = a % b; + EXPECT_EQ(0, result_q); + EXPECT_EQ(a, result_r); + // Restore. + swap(q, a); + swap(b, a); + + // Try a large remainder. + b = a / 2 + 1; + absl::uint128 expected_r = + absl::MakeUint128(0x29876d3a0e38ea61, 0xdf92cba98c83ffff); + // Sanity checks. + ASSERT_EQ(a / 2 - 1, expected_r); + ASSERT_EQ(a, b + expected_r); + result_q = a / b; + result_r = a % b; + EXPECT_EQ(1, result_q); + EXPECT_EQ(expected_r, result_r); +} + +TEST(Uint128, DivideAndModRandomInputs) { + const int kNumIters = 1 << 18; + std::minstd_rand random(testing::UnitTest::GetInstance()->random_seed()); + std::uniform_int_distribution uniform_uint64; + for (int i = 0; i < kNumIters; ++i) { + const absl::uint128 a = + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random)); + const absl::uint128 b = + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random)); + if (b == 0) { + continue; // Avoid a div-by-zero. + } + const absl::uint128 q = a / b; + const absl::uint128 r = a % b; + ASSERT_EQ(a, b * q + r); + } +} + +TEST(Uint128, ConstexprTest) { + constexpr absl::uint128 zero = absl::uint128(); + constexpr absl::uint128 one = 1; + constexpr absl::uint128 minus_two = -2; + EXPECT_EQ(zero, absl::uint128(0)); + EXPECT_EQ(one, absl::uint128(1)); + EXPECT_EQ(minus_two, absl::MakeUint128(-1, -2)); +} + +} // namespace diff --git a/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.cc b/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.cc new file mode 100644 index 00000000000..6ee2b109088 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.cc @@ -0,0 +1,34 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/ostringstream.h" + +namespace absl { +namespace strings_internal { + +OStringStream::Buf::int_type OStringStream::overflow(int c) { + assert(s_); + if (!Buf::traits_type::eq_int_type(c, Buf::traits_type::eof())) + s_->push_back(static_cast(c)); + return 1; +} + +std::streamsize OStringStream::xsputn(const char* s, std::streamsize n) { + assert(s_); + s_->append(s, n); + return n; +} + +} // namespace strings_internal +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.h b/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.h new file mode 100644 index 00000000000..6e1325b9140 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream.h @@ -0,0 +1,87 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_ +#define ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_ + +#include +#include +#include +#include + +#include "absl/base/port.h" + +namespace absl { +namespace strings_internal { + +// The same as std::ostringstream but appends to a user-specified std::string, +// and is faster. It is ~70% faster to create, ~50% faster to write to, and +// completely free to extract the result std::string. +// +// std::string s; +// OStringStream strm(&s); +// strm << 42 << ' ' << 3.14; // appends to `s` +// +// The stream object doesn't have to be named. Starting from C++11 operator<< +// works with rvalues of std::ostream. +// +// std::string s; +// OStringStream(&s) << 42 << ' ' << 3.14; // appends to `s` +// +// OStringStream is faster to create than std::ostringstream but it's still +// relatively slow. Avoid creating multiple streams where a single stream will +// do. +// +// Creates unnecessary instances of OStringStream: slow. +// +// std::string s; +// OStringStream(&s) << 42; +// OStringStream(&s) << ' '; +// OStringStream(&s) << 3.14; +// +// Creates a single instance of OStringStream and reuses it: fast. +// +// std::string s; +// OStringStream strm(&s); +// strm << 42; +// strm << ' '; +// strm << 3.14; +// +// Note: flush() has no effect. No reason to call it. +class OStringStream : private std::basic_streambuf, public std::ostream { + public: + // The argument can be null, in which case you'll need to call str(p) with a + // non-null argument before you can write to the stream. + // + // The destructor of OStringStream doesn't use the std::string. It's OK to destroy + // the std::string before the stream. + explicit OStringStream(std::string* s) : std::ostream(this), s_(s) {} + + std::string* str() { return s_; } + const std::string* str() const { return s_; } + void str(std::string* s) { s_ = s; } + + private: + using Buf = std::basic_streambuf; + + Buf::int_type overflow(int c) override; + std::streamsize xsputn(const char* s, std::streamsize n) override; + + std::string* s_; +}; + +} // namespace strings_internal +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream_test.cc new file mode 100644 index 00000000000..069a0e1fbbb --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/internal/ostringstream_test.cc @@ -0,0 +1,102 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/ostringstream.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace { + +TEST(OStringStream, IsOStream) { + static_assert( + std::is_base_of(), + ""); +} + +TEST(OStringStream, ConstructDestroy) { + { + absl::strings_internal::OStringStream strm(nullptr); + EXPECT_EQ(nullptr, strm.str()); + } + { + std::string s = "abc"; + { + absl::strings_internal::OStringStream strm(&s); + EXPECT_EQ(&s, strm.str()); + } + EXPECT_EQ("abc", s); + } + { + std::unique_ptr s(new std::string); + absl::strings_internal::OStringStream strm(s.get()); + s.reset(); + } +} + +TEST(OStringStream, Str) { + std::string s1; + absl::strings_internal::OStringStream strm(&s1); + const absl::strings_internal::OStringStream& c_strm(strm); + + static_assert(std::is_same(), ""); + static_assert(std::is_same(), ""); + + EXPECT_EQ(&s1, strm.str()); + EXPECT_EQ(&s1, c_strm.str()); + + strm.str(&s1); + EXPECT_EQ(&s1, strm.str()); + EXPECT_EQ(&s1, c_strm.str()); + + std::string s2; + strm.str(&s2); + EXPECT_EQ(&s2, strm.str()); + EXPECT_EQ(&s2, c_strm.str()); + + strm.str(nullptr); + EXPECT_EQ(nullptr, strm.str()); + EXPECT_EQ(nullptr, c_strm.str()); +} + +TEST(OStreamStream, WriteToLValue) { + std::string s = "abc"; + { + absl::strings_internal::OStringStream strm(&s); + EXPECT_EQ("abc", s); + strm << ""; + EXPECT_EQ("abc", s); + strm << 42; + EXPECT_EQ("abc42", s); + strm << 'x' << 'y'; + EXPECT_EQ("abc42xy", s); + } + EXPECT_EQ("abc42xy", s); +} + +TEST(OStreamStream, WriteToRValue) { + std::string s = "abc"; + absl::strings_internal::OStringStream(&s) << ""; + EXPECT_EQ("abc", s); + absl::strings_internal::OStringStream(&s) << 42; + EXPECT_EQ("abc42", s); + absl::strings_internal::OStringStream(&s) << 'x' << 'y'; + EXPECT_EQ("abc42xy", s); +} + +} // namespace diff --git a/Firestore/third_party/abseil-cpp/absl/strings/internal/str_join_internal.h b/Firestore/third_party/abseil-cpp/absl/strings/internal/str_join_internal.h new file mode 100644 index 00000000000..c5fdc287cba --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/internal/str_join_internal.h @@ -0,0 +1,309 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This file declares INTERNAL parts of the Join API that are inlined/templated +// or otherwise need to be available at compile time. The main abstractions +// defined in this file are: +// +// - A handful of default Formatters +// - JoinAlgorithm() overloads +// - JoinRange() overloads +// - JoinTuple() +// +// DO NOT INCLUDE THIS FILE DIRECTLY. Use this file by including +// absl/strings/str_join.h +// +// IWYU pragma: private, include "absl/strings/str_join.h" + +#ifndef ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_ +#define ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/strings/internal/ostringstream.h" +#include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/str_cat.h" + +namespace absl { +namespace strings_internal { + +// +// Formatter objects +// +// The following are implementation classes for standard Formatter objects. The +// factory functions that users will call to create and use these formatters are +// defined and documented in strings/join.h. +// + +// The default formatter. Converts alpha-numeric types to strings. +struct AlphaNumFormatterImpl { + // This template is needed in order to support passing in a dereferenced + // vector::iterator + template + void operator()(std::string* out, const T& t) const { + StrAppend(out, AlphaNum(t)); + } + + void operator()(std::string* out, const AlphaNum& t) const { + StrAppend(out, t); + } +}; + +// A type that's used to overload the JoinAlgorithm() function (defined below) +// for ranges that do not require additional formatting (e.g., a range of +// strings). + +struct NoFormatter : public AlphaNumFormatterImpl {}; + +// Formats types to strings using the << operator. +class StreamFormatterImpl { + public: + // The method isn't const because it mutates state. Making it const will + // render StreamFormatterImpl thread-hostile. + template + void operator()(std::string* out, const T& t) { + // The stream is created lazily to avoid paying the relatively high cost + // of its construction when joining an empty range. + if (strm_) { + strm_->clear(); // clear the bad, fail and eof bits in case they were set + strm_->str(out); + } else { + strm_.reset(new strings_internal::OStringStream(out)); + } + *strm_ << t; + } + + private: + std::unique_ptr strm_; +}; + +// Formats a std::pair<>. The 'first' member is formatted using f1_ and the +// 'second' member is formatted using f2_. sep_ is the separator. +template +class PairFormatterImpl { + public: + PairFormatterImpl(F1 f1, absl::string_view sep, F2 f2) + : f1_(std::move(f1)), sep_(sep), f2_(std::move(f2)) {} + + template + void operator()(std::string* out, const T& p) { + f1_(out, p.first); + out->append(sep_); + f2_(out, p.second); + } + + template + void operator()(std::string* out, const T& p) const { + f1_(out, p.first); + out->append(sep_); + f2_(out, p.second); + } + + private: + F1 f1_; + std::string sep_; + F2 f2_; +}; + +// Wraps another formatter and dereferences the argument to operator() then +// passes the dereferenced argument to the wrapped formatter. This can be +// useful, for example, to join a std::vector. +template +class DereferenceFormatterImpl { + public: + DereferenceFormatterImpl() : f_() {} + explicit DereferenceFormatterImpl(Formatter&& f) + : f_(std::forward(f)) {} + + template + void operator()(std::string* out, const T& t) { + f_(out, *t); + } + + template + void operator()(std::string* out, const T& t) const { + f_(out, *t); + } + + private: + Formatter f_; +}; + +// DefaultFormatter is a traits class that selects a default Formatter to use +// for the given type T. The ::Type member names the Formatter to use. This is +// used by the strings::Join() functions that do NOT take a Formatter argument, +// in which case a default Formatter must be chosen. +// +// AlphaNumFormatterImpl is the default in the base template, followed by +// specializations for other types. +template +struct DefaultFormatter { + typedef AlphaNumFormatterImpl Type; +}; +template <> +struct DefaultFormatter { + typedef AlphaNumFormatterImpl Type; +}; +template <> +struct DefaultFormatter { + typedef AlphaNumFormatterImpl Type; +}; +template <> +struct DefaultFormatter { + typedef NoFormatter Type; +}; +template <> +struct DefaultFormatter { + typedef NoFormatter Type; +}; +template +struct DefaultFormatter { + typedef DereferenceFormatterImpl::Type> + Type; +}; + +template +struct DefaultFormatter> + : public DefaultFormatter {}; + +// +// JoinAlgorithm() functions +// + +// The main joining algorithm. This simply joins the elements in the given +// iterator range, each separated by the given separator, into an output std::string, +// and formats each element using the provided Formatter object. +template +std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view s, + Formatter&& f) { + std::string result; + absl::string_view sep(""); + for (Iterator it = start; it != end; ++it) { + result.append(sep.data(), sep.size()); + f(&result, *it); + sep = s; + } + return result; +} + +// A joining algorithm that's optimized for a forward iterator range of +// std::string-like objects that do not need any additional formatting. This is to +// optimize the common case of joining, say, a std::vector or a +// std::vector. +// +// This is an overload of the previous JoinAlgorithm() function. Here the +// Formatter argument is of type NoFormatter. Since NoFormatter is an internal +// type, this overload is only invoked when strings::Join() is called with a +// range of std::string-like objects (e.g., std::string, absl::string_view), and an +// explicit Formatter argument was NOT specified. +// +// The optimization is that the needed space will be reserved in the output +// std::string to avoid the need to resize while appending. To do this, the iterator +// range will be traversed twice: once to calculate the total needed size, and +// then again to copy the elements and delimiters to the output std::string. +template ::iterator_category, + std::forward_iterator_tag>::value>::type> +std::string JoinAlgorithm(Iterator start, Iterator end, absl::string_view s, + NoFormatter) { + std::string result; + if (start != end) { + // Sums size + size_t result_size = start->size(); + for (Iterator it = start; ++it != end;) { + result_size += s.size(); + result_size += it->size(); + } + + STLStringResizeUninitialized(&result, result_size); + + // Joins strings + char* result_buf = &*result.begin(); + memcpy(result_buf, start->data(), start->size()); + result_buf += start->size(); + for (Iterator it = start; ++it != end;) { + memcpy(result_buf, s.data(), s.size()); + result_buf += s.size(); + memcpy(result_buf, it->data(), it->size()); + result_buf += it->size(); + } + } + + return result; +} + +// JoinTupleLoop implements a loop over the elements of a std::tuple, which +// are heterogeneous. The primary template matches the tuple interior case. It +// continues the iteration after appending a separator (for nonzero indices) +// and formatting an element of the tuple. The specialization for the I=N case +// matches the end-of-tuple, and terminates the iteration. +template +struct JoinTupleLoop { + template + void operator()(std::string* out, const Tup& tup, absl::string_view sep, + Formatter&& fmt) { + if (I > 0) out->append(sep.data(), sep.size()); + fmt(out, std::get(tup)); + JoinTupleLoop()(out, tup, sep, fmt); + } +}; +template +struct JoinTupleLoop { + template + void operator()(std::string*, const Tup&, absl::string_view, Formatter&&) {} +}; + +template +std::string JoinAlgorithm(const std::tuple& tup, absl::string_view sep, + Formatter&& fmt) { + std::string result; + JoinTupleLoop<0, sizeof...(T)>()(&result, tup, sep, fmt); + return result; +} + +template +std::string JoinRange(Iterator first, Iterator last, absl::string_view separator) { + // No formatter was explicitly given, so a default must be chosen. + typedef typename std::iterator_traits::value_type ValueType; + typedef typename DefaultFormatter::Type Formatter; + return JoinAlgorithm(first, last, separator, Formatter()); +} + +template +std::string JoinRange(const Range& range, absl::string_view separator, + Formatter&& fmt) { + using std::begin; + using std::end; + return JoinAlgorithm(begin(range), end(range), separator, fmt); +} + +template +std::string JoinRange(const Range& range, absl::string_view separator) { + using std::begin; + using std::end; + return JoinRange(begin(range), end(range), separator); +} + +} // namespace strings_internal +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_JOIN_INTERNAL_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/internal/str_split_internal.h b/Firestore/third_party/abseil-cpp/absl/strings/internal/str_split_internal.h new file mode 100644 index 00000000000..a1b10f3addc --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/internal/str_split_internal.h @@ -0,0 +1,435 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// This file declares INTERNAL parts of the Split API that are inline/templated +// or otherwise need to be available at compile time. The main abstractions +// defined in here are +// +// - ConvertibleToStringView +// - SplitIterator<> +// - Splitter<> +// +// DO NOT INCLUDE THIS FILE DIRECTLY. Use this file by including +// absl/strings/str_split.h. +// +// IWYU pragma: private, include "absl/strings/str_split.h" + +#ifndef ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_ +#define ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/base/port.h" +#include "absl/meta/type_traits.h" +#include "absl/strings/string_view.h" + +#ifdef _GLIBCXX_DEBUG +#include "absl/strings/internal/stl_type_traits.h" +#endif // _GLIBCXX_DEBUG + +namespace absl { +namespace strings_internal { + +// This class is implicitly constructible from everything that absl::string_view +// is implicitly constructible from. If it's constructed from a temporary +// std::string, the data is moved into a data member so its lifetime matches that of +// the ConvertibleToStringView instance. +class ConvertibleToStringView { + public: + ConvertibleToStringView(const char* s) // NOLINT(runtime/explicit) + : value_(s) {} + ConvertibleToStringView(char* s) : value_(s) {} // NOLINT(runtime/explicit) + ConvertibleToStringView(absl::string_view s) // NOLINT(runtime/explicit) + : value_(s) {} + ConvertibleToStringView(const std::string& s) // NOLINT(runtime/explicit) + : value_(s) {} + + // Matches rvalue strings and moves their data to a member. +ConvertibleToStringView(std::string&& s) // NOLINT(runtime/explicit) + : copy_(std::move(s)), value_(copy_) {} + + ConvertibleToStringView(const ConvertibleToStringView& other) + : copy_(other.copy_), + value_(other.IsSelfReferential() ? copy_ : other.value_) {} + + ConvertibleToStringView(ConvertibleToStringView&& other) { + StealMembers(std::move(other)); + } + + ConvertibleToStringView& operator=(ConvertibleToStringView other) { + StealMembers(std::move(other)); + return *this; + } + + absl::string_view value() const { return value_; } + + private: + // Returns true if ctsp's value refers to its internal copy_ member. + bool IsSelfReferential() const { return value_.data() == copy_.data(); } + + void StealMembers(ConvertibleToStringView&& other) { + if (other.IsSelfReferential()) { + copy_ = std::move(other.copy_); + value_ = copy_; + other.value_ = other.copy_; + } else { + value_ = other.value_; + } + } + + // Holds the data moved from temporary std::string arguments. Declared first so + // that 'value' can refer to 'copy_'. + std::string copy_; + absl::string_view value_; +}; + +// An iterator that enumerates the parts of a std::string from a Splitter. The text +// to be split, the Delimiter, and the Predicate are all taken from the given +// Splitter object. Iterators may only be compared if they refer to the same +// Splitter instance. +// +// This class is NOT part of the public splitting API. +template +class SplitIterator { + public: + using iterator_category = std::input_iterator_tag; + using value_type = absl::string_view; + using difference_type = ptrdiff_t; + using pointer = const value_type*; + using reference = const value_type&; + + enum State { kInitState, kLastState, kEndState }; + SplitIterator(State state, const Splitter* splitter) + : pos_(0), + state_(state), + splitter_(splitter), + delimiter_(splitter->delimiter()), + predicate_(splitter->predicate()) { + // Hack to maintain backward compatibility. This one block makes it so an + // empty absl::string_view whose .data() happens to be nullptr behaves + // *differently* from an otherwise empty absl::string_view whose .data() is + // not nullptr. This is an undesirable difference in general, but this + // behavior is maintained to avoid breaking existing code that happens to + // depend on this old behavior/bug. Perhaps it will be fixed one day. The + // difference in behavior is as follows: + // Split(absl::string_view(""), '-'); // {""} + // Split(absl::string_view(), '-'); // {} + if (splitter_->text().data() == nullptr) { + state_ = kEndState; + pos_ = splitter_->text().size(); + return; + } + + if (state_ == kEndState) { + pos_ = splitter_->text().size(); + } else { + ++(*this); + } + } + + bool at_end() const { return state_ == kEndState; } + + reference operator*() const { return curr_; } + pointer operator->() const { return &curr_; } + + SplitIterator& operator++() { + do { + if (state_ == kLastState) { + state_ = kEndState; + return *this; + } + const absl::string_view text = splitter_->text(); + const absl::string_view d = delimiter_.Find(text, pos_); + if (d.data() == text.end()) state_ = kLastState; + curr_ = text.substr(pos_, d.data() - (text.data() + pos_)); + pos_ += curr_.size() + d.size(); + } while (!predicate_(curr_)); + return *this; + } + + SplitIterator operator++(int) { + SplitIterator old(*this); + ++(*this); + return old; + } + + friend bool operator==(const SplitIterator& a, const SplitIterator& b) { + return a.state_ == b.state_ && a.pos_ == b.pos_; + } + + friend bool operator!=(const SplitIterator& a, const SplitIterator& b) { + return !(a == b); + } + + private: + size_t pos_; + State state_; + absl::string_view curr_; + const Splitter* splitter_; + typename Splitter::DelimiterType delimiter_; + typename Splitter::PredicateType predicate_; +}; + +// HasMappedType::value is true iff there exists a type T::mapped_type. +template +struct HasMappedType : std::false_type {}; +template +struct HasMappedType> + : std::true_type {}; + +// HasValueType::value is true iff there exists a type T::value_type. +template +struct HasValueType : std::false_type {}; +template +struct HasValueType> : std::true_type { +}; + +// HasConstIterator::value is true iff there exists a type T::const_iterator. +template +struct HasConstIterator : std::false_type {}; +template +struct HasConstIterator> + : std::true_type {}; + +// IsInitializerList::value is true iff T is an std::initializer_list. More +// details below in Splitter<> where this is used. +std::false_type IsInitializerListDispatch(...); // default: No +template +std::true_type IsInitializerListDispatch(std::initializer_list*); +template +struct IsInitializerList + : decltype(IsInitializerListDispatch(static_cast(nullptr))) {}; + +// A SplitterIsConvertibleTo::type alias exists iff the specified condition +// is true for type 'C'. +// +// Restricts conversion to container-like types (by testing for the presence of +// a const_iterator member type) and also to disable conversion to an +// std::initializer_list (which also has a const_iterator). Otherwise, code +// compiled in C++11 will get an error due to ambiguous conversion paths (in +// C++11 std::vector::operator= is overloaded to take either a std::vector +// or an std::initializer_list). +template +struct SplitterIsConvertibleTo + : std::enable_if< +#ifdef _GLIBCXX_DEBUG + !IsStrictlyBaseOfAndConvertibleToSTLContainer::value && +#endif // _GLIBCXX_DEBUG + !IsInitializerList::value && HasValueType::value && + HasConstIterator::value> { +}; + +// This class implements the range that is returned by absl::StrSplit(). This +// class has templated conversion operators that allow it to be implicitly +// converted to a variety of types that the caller may have specified on the +// left-hand side of an assignment. +// +// The main interface for interacting with this class is through its implicit +// conversion operators. However, this class may also be used like a container +// in that it has .begin() and .end() member functions. It may also be used +// within a range-for loop. +// +// Output containers can be collections of any type that is constructible from +// an absl::string_view. +// +// An Predicate functor may be supplied. This predicate will be used to filter +// the split strings: only strings for which the predicate returns true will be +// kept. A Predicate object is any unary functor that takes an absl::string_view +// and returns bool. +template +class Splitter { + public: + using DelimiterType = Delimiter; + using PredicateType = Predicate; + using const_iterator = strings_internal::SplitIterator; + using value_type = typename std::iterator_traits::value_type; + + Splitter(ConvertibleToStringView input_text, Delimiter d, Predicate p) + : text_(std::move(input_text)), + delimiter_(std::move(d)), + predicate_(std::move(p)) {} + + absl::string_view text() const { return text_.value(); } + const Delimiter& delimiter() const { return delimiter_; } + const Predicate& predicate() const { return predicate_; } + + // Range functions that iterate the split substrings as absl::string_view + // objects. These methods enable a Splitter to be used in a range-based for + // loop. + const_iterator begin() const { return {const_iterator::kInitState, this}; } + const_iterator end() const { return {const_iterator::kEndState, this}; } + + // An implicit conversion operator that is restricted to only those containers + // that the splitter is convertible to. + template ::type> + operator Container() const { // NOLINT(runtime/explicit) + return ConvertToContainer::value>()(*this); + } + + // Returns a pair with its .first and .second members set to the first two + // strings returned by the begin() iterator. Either/both of .first and .second + // will be constructed with empty strings if the iterator doesn't have a + // corresponding value. + template + operator std::pair() const { // NOLINT(runtime/explicit) + absl::string_view first, second; + auto it = begin(); + if (it != end()) { + first = *it; + if (++it != end()) { + second = *it; + } + } + return {First(first), Second(second)}; + } + + private: + // ConvertToContainer is a functor converting a Splitter to the requested + // Container of ValueType. It is specialized below to optimize splitting to + // certain combinations of Container and ValueType. + // + // This base template handles the generic case of storing the split results in + // the requested non-map-like container and converting the split substrings to + // the requested type. + template + struct ConvertToContainer { + Container operator()(const Splitter& splitter) const { + Container c; + auto it = std::inserter(c, c.end()); + for (const auto sp : splitter) { + *it++ = ValueType(sp); + } + return c; + } + }; + + // Partial specialization for a std::vector. + // + // Optimized for the common case of splitting to a + // std::vector. In this case we first split the results to + // a small array of absl::string_view on the stack, to reduce reallocations. + template + struct ConvertToContainer, + absl::string_view, false> { + std::vector operator()( + const Splitter& splitter) const { + struct raw_view { + const char* data; + size_t size; + operator absl::string_view() const { // NOLINT(runtime/explicit) + return {data, size}; + } + }; + std::vector v; + std::array ar; + for (auto it = splitter.begin(); !it.at_end();) { + size_t index = 0; + do { + ar[index].data = it->data(); + ar[index].size = it->size(); + ++it; + } while (++index != ar.size() && !it.at_end()); + v.insert(v.end(), ar.begin(), ar.begin() + index); + } + return v; + } + }; + + // Partial specialization for a std::vector. + // + // Optimized for the common case of splitting to a std::vector. In + // this case we first split the results to a std::vector so + // the returned std::vector can have space reserved to avoid std::string + // moves. + template + struct ConvertToContainer, std::string, false> { + std::vector operator()(const Splitter& splitter) const { + const std::vector v = splitter; + return std::vector(v.begin(), v.end()); + } + }; + + // Partial specialization for containers of pairs (e.g., maps). + // + // The algorithm is to insert a new pair into the map for each even-numbered + // item, with the even-numbered item as the key with a default-constructed + // value. Each odd-numbered item will then be assigned to the last pair's + // value. + template + struct ConvertToContainer, true> { + Container operator()(const Splitter& splitter) const { + Container m; + typename Container::iterator it; + bool insert = true; + for (const auto sp : splitter) { + if (insert) { + it = Inserter::Insert(&m, First(sp), Second()); + } else { + it->second = Second(sp); + } + insert = !insert; + } + return m; + } + + // Inserts the key and value into the given map, returning an iterator to + // the inserted item. Specialized for std::map and std::multimap to use + // emplace() and adapt emplace()'s return value. + template + struct Inserter { + using M = Map; + template + static typename M::iterator Insert(M* m, Args&&... args) { + return m->insert(std::make_pair(std::forward(args)...)).first; + } + }; + + template + struct Inserter> { + using M = std::map; + template + static typename M::iterator Insert(M* m, Args&&... args) { + return m->emplace(std::make_pair(std::forward(args)...)).first; + } + }; + + template + struct Inserter> { + using M = std::multimap; + template + static typename M::iterator Insert(M* m, Args&&... args) { + return m->emplace(std::make_pair(std::forward(args)...)); + } + }; + }; + + ConvertibleToStringView text_; + Delimiter delimiter_; + Predicate predicate_; +}; + +} // namespace strings_internal +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/numbers.cc b/Firestore/third_party/abseil-cpp/absl/strings/numbers.cc new file mode 100644 index 00000000000..b4140b3605b --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/numbers.cc @@ -0,0 +1,919 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains std::string processing functions related to +// numeric values. + +#include "absl/strings/numbers.h" + +#include +#include +#include // for DBL_DIG and FLT_DIG +#include // for HUGE_VAL +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/ascii.h" +#include "absl/strings/internal/memutil.h" +#include "absl/strings/str_cat.h" + +namespace absl { + +bool SimpleAtof(absl::string_view str, float* value) { + *value = 0.0; + if (str.empty()) return false; + char buf[32]; + std::unique_ptr bigbuf; + char* ptr = buf; + if (str.size() > sizeof(buf) - 1) { + bigbuf.reset(new char[str.size() + 1]); + ptr = bigbuf.get(); + } + memcpy(ptr, str.data(), str.size()); + ptr[str.size()] = '\0'; + + char* endptr; + *value = strtof(ptr, &endptr); + if (endptr != ptr) { + while (absl::ascii_isspace(*endptr)) ++endptr; + } + // Ignore range errors from strtod/strtof. + // The values it returns on underflow and + // overflow are the right fallback in a + // robust setting. + return *ptr != '\0' && *endptr == '\0'; +} + +bool SimpleAtod(absl::string_view str, double* value) { + *value = 0.0; + if (str.empty()) return false; + char buf[32]; + std::unique_ptr bigbuf; + char* ptr = buf; + if (str.size() > sizeof(buf) - 1) { + bigbuf.reset(new char[str.size() + 1]); + ptr = bigbuf.get(); + } + memcpy(ptr, str.data(), str.size()); + ptr[str.size()] = '\0'; + + char* endptr; + *value = strtod(ptr, &endptr); + if (endptr != ptr) { + while (absl::ascii_isspace(*endptr)) ++endptr; + } + // Ignore range errors from strtod. The values it + // returns on underflow and overflow are the right + // fallback in a robust setting. + return *ptr != '\0' && *endptr == '\0'; +} + +namespace { + +// TODO(rogeeff): replace with the real released thing once we figure out what +// it is. +inline bool CaseEqual(absl::string_view piece1, absl::string_view piece2) { + return (piece1.size() == piece2.size() && + 0 == strings_internal::memcasecmp(piece1.data(), piece2.data(), + piece1.size())); +} + +// Writes a two-character representation of 'i' to 'buf'. 'i' must be in the +// range 0 <= i < 100, and buf must have space for two characters. Example: +// char buf[2]; +// PutTwoDigits(42, buf); +// // buf[0] == '4' +// // buf[1] == '2' +inline void PutTwoDigits(size_t i, char* buf) { + static const char two_ASCII_digits[100][2] = { + {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, + {'0', '5'}, {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, + {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, + {'1', '5'}, {'1', '6'}, {'1', '7'}, {'1', '8'}, {'1', '9'}, + {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, + {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, + {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, + {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, + {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, + {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'}, + {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, + {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, + {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, + {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, + {'7', '0'}, {'7', '1'}, {'7', '2'}, {'7', '3'}, {'7', '4'}, + {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, + {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, {'8', '4'}, + {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, + {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, + {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'} + }; + assert(i < 100); + memcpy(buf, two_ASCII_digits[i], 2); +} + +} // namespace + +bool SimpleAtob(absl::string_view str, bool* value) { + ABSL_RAW_CHECK(value != nullptr, "Output pointer must not be nullptr."); + if (CaseEqual(str, "true") || CaseEqual(str, "t") || + CaseEqual(str, "yes") || CaseEqual(str, "y") || + CaseEqual(str, "1")) { + *value = true; + return true; + } + if (CaseEqual(str, "false") || CaseEqual(str, "f") || + CaseEqual(str, "no") || CaseEqual(str, "n") || + CaseEqual(str, "0")) { + *value = false; + return true; + } + return false; +} + +// ---------------------------------------------------------------------- +// FastIntToBuffer() overloads +// +// Like the Fast*ToBuffer() functions above, these are intended for speed. +// Unlike the Fast*ToBuffer() functions, however, these functions write +// their output to the beginning of the buffer. The caller is responsible +// for ensuring that the buffer has enough space to hold the output. +// +// Returns a pointer to the end of the std::string (i.e. the null character +// terminating the std::string). +// ---------------------------------------------------------------------- + +namespace { + +// Used to optimize printing a decimal number's final digit. +const char one_ASCII_final_digits[10][2] { + {'0', 0}, {'1', 0}, {'2', 0}, {'3', 0}, {'4', 0}, + {'5', 0}, {'6', 0}, {'7', 0}, {'8', 0}, {'9', 0}, +}; + +} // namespace + +char* numbers_internal::FastIntToBuffer(uint32_t i, char* buffer) { + uint32_t digits; + // The idea of this implementation is to trim the number of divides to as few + // as possible, and also reducing memory stores and branches, by going in + // steps of two digits at a time rather than one whenever possible. + // The huge-number case is first, in the hopes that the compiler will output + // that case in one branch-free block of code, and only output conditional + // branches into it from below. + if (i >= 1000000000) { // >= 1,000,000,000 + digits = i / 100000000; // 100,000,000 + i -= digits * 100000000; + PutTwoDigits(digits, buffer); + buffer += 2; + lt100_000_000: + digits = i / 1000000; // 1,000,000 + i -= digits * 1000000; + PutTwoDigits(digits, buffer); + buffer += 2; + lt1_000_000: + digits = i / 10000; // 10,000 + i -= digits * 10000; + PutTwoDigits(digits, buffer); + buffer += 2; + lt10_000: + digits = i / 100; + i -= digits * 100; + PutTwoDigits(digits, buffer); + buffer += 2; + lt100: + digits = i; + PutTwoDigits(digits, buffer); + buffer += 2; + *buffer = 0; + return buffer; + } + + if (i < 100) { + digits = i; + if (i >= 10) goto lt100; + memcpy(buffer, one_ASCII_final_digits[i], 2); + return buffer + 1; + } + if (i < 10000) { // 10,000 + if (i >= 1000) goto lt10_000; + digits = i / 100; + i -= digits * 100; + *buffer++ = '0' + digits; + goto lt100; + } + if (i < 1000000) { // 1,000,000 + if (i >= 100000) goto lt1_000_000; + digits = i / 10000; // 10,000 + i -= digits * 10000; + *buffer++ = '0' + digits; + goto lt10_000; + } + if (i < 100000000) { // 100,000,000 + if (i >= 10000000) goto lt100_000_000; + digits = i / 1000000; // 1,000,000 + i -= digits * 1000000; + *buffer++ = '0' + digits; + goto lt1_000_000; + } + // we already know that i < 1,000,000,000 + digits = i / 100000000; // 100,000,000 + i -= digits * 100000000; + *buffer++ = '0' + digits; + goto lt100_000_000; +} + +char* numbers_internal::FastIntToBuffer(int32_t i, char* buffer) { + uint32_t u = i; + if (i < 0) { + *buffer++ = '-'; + // We need to do the negation in modular (i.e., "unsigned") + // arithmetic; MSVC++ apprently warns for plain "-u", so + // we write the equivalent expression "0 - u" instead. + u = 0 - u; + } + return numbers_internal::FastIntToBuffer(u, buffer); +} + +char* numbers_internal::FastIntToBuffer(uint64_t i, char* buffer) { + uint32_t u32 = static_cast(i); + if (u32 == i) return numbers_internal::FastIntToBuffer(u32, buffer); + + // Here we know i has at least 10 decimal digits. + uint64_t top_1to11 = i / 1000000000; + u32 = static_cast(i - top_1to11 * 1000000000); + uint32_t top_1to11_32 = static_cast(top_1to11); + + if (top_1to11_32 == top_1to11) { + buffer = numbers_internal::FastIntToBuffer(top_1to11_32, buffer); + } else { + // top_1to11 has more than 32 bits too; print it in two steps. + uint32_t top_8to9 = static_cast(top_1to11 / 100); + uint32_t mid_2 = static_cast(top_1to11 - top_8to9 * 100); + buffer = numbers_internal::FastIntToBuffer(top_8to9, buffer); + PutTwoDigits(mid_2, buffer); + buffer += 2; + } + + // We have only 9 digits now, again the maximum uint32_t can handle fully. + uint32_t digits = u32 / 10000000; // 10,000,000 + u32 -= digits * 10000000; + PutTwoDigits(digits, buffer); + buffer += 2; + digits = u32 / 100000; // 100,000 + u32 -= digits * 100000; + PutTwoDigits(digits, buffer); + buffer += 2; + digits = u32 / 1000; // 1,000 + u32 -= digits * 1000; + PutTwoDigits(digits, buffer); + buffer += 2; + digits = u32 / 10; + u32 -= digits * 10; + PutTwoDigits(digits, buffer); + buffer += 2; + memcpy(buffer, one_ASCII_final_digits[u32], 2); + return buffer + 1; +} + +char* numbers_internal::FastIntToBuffer(int64_t i, char* buffer) { + uint64_t u = i; + if (i < 0) { + *buffer++ = '-'; + u = 0 - u; + } + return numbers_internal::FastIntToBuffer(u, buffer); +} + +// Returns the number of leading 0 bits in a 64-bit value. +// TODO(jorg): Replace with builtin_clzll if available. +// Are we shipping util/bits in absl? +static inline int CountLeadingZeros64(uint64_t n) { + int zeroes = 60; + if (n >> 32) zeroes -= 32, n >>= 32; + if (n >> 16) zeroes -= 16, n >>= 16; + if (n >> 8) zeroes -= 8, n >>= 8; + if (n >> 4) zeroes -= 4, n >>= 4; + return "\4\3\2\2\1\1\1\1\0\0\0\0\0\0\0\0"[n] + zeroes; +} + +// Given a 128-bit number expressed as a pair of uint64_t, high half first, +// return that number multiplied by the given 32-bit value. If the result is +// too large to fit in a 128-bit number, divide it by 2 until it fits. +static std::pair Mul32(std::pair num, + uint32_t mul) { + uint64_t bits0_31 = num.second & 0xFFFFFFFF; + uint64_t bits32_63 = num.second >> 32; + uint64_t bits64_95 = num.first & 0xFFFFFFFF; + uint64_t bits96_127 = num.first >> 32; + + // The picture so far: each of these 64-bit values has only the lower 32 bits + // filled in. + // bits96_127: [ 00000000 xxxxxxxx ] + // bits64_95: [ 00000000 xxxxxxxx ] + // bits32_63: [ 00000000 xxxxxxxx ] + // bits0_31: [ 00000000 xxxxxxxx ] + + bits0_31 *= mul; + bits32_63 *= mul; + bits64_95 *= mul; + bits96_127 *= mul; + + // Now the top halves may also have value, though all 64 of their bits will + // never be set at the same time, since they are a result of a 32x32 bit + // multiply. This makes the carry calculation slightly easier. + // bits96_127: [ mmmmmmmm | mmmmmmmm ] + // bits64_95: [ | mmmmmmmm mmmmmmmm | ] + // bits32_63: | [ mmmmmmmm | mmmmmmmm ] + // bits0_31: | [ | mmmmmmmm mmmmmmmm ] + // eventually: [ bits128_up | ...bits64_127.... | ..bits0_63... ] + + uint64_t bits0_63 = bits0_31 + (bits32_63 << 32); + uint64_t bits64_127 = bits64_95 + (bits96_127 << 32) + (bits32_63 >> 32) + + (bits0_63 < bits0_31); + uint64_t bits128_up = (bits96_127 >> 32) + (bits64_127 < bits64_95); + if (bits128_up == 0) return {bits64_127, bits0_63}; + + int shift = 64 - CountLeadingZeros64(bits128_up); + uint64_t lo = (bits0_63 >> shift) + (bits64_127 << (64 - shift)); + uint64_t hi = (bits64_127 >> shift) + (bits128_up << (64 - shift)); + return {hi, lo}; +} + +// Compute num * 5 ^ expfive, and return the first 128 bits of the result, +// where the first bit is always a one. So PowFive(1, 0) starts 0b100000, +// PowFive(1, 1) starts 0b101000, PowFive(1, 2) starts 0b110010, etc. +static std::pair PowFive(uint64_t num, int expfive) { + std::pair result = {num, 0}; + while (expfive >= 13) { + // 5^13 is the highest power of five that will fit in a 32-bit integer. + result = Mul32(result, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5); + expfive -= 13; + } + constexpr int powers_of_five[13] = { + 1, + 5, + 5 * 5, + 5 * 5 * 5, + 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5, + 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5}; + result = Mul32(result, powers_of_five[expfive & 15]); + int shift = CountLeadingZeros64(result.first); + if (shift != 0) { + result.first = (result.first << shift) + (result.second >> (64 - shift)); + result.second = (result.second << shift); + } + return result; +} + +struct ExpDigits { + int32_t exponent; + char digits[6]; +}; + +// SplitToSix converts value, a positive double-precision floating-point number, +// into a base-10 exponent and 6 ASCII digits, where the first digit is never +// zero. For example, SplitToSix(1) returns an exponent of zero and a digits +// array of {'1', '0', '0', '0', '0', '0'}. If value is exactly halfway between +// two possible representations, e.g. value = 100000.5, then "round to even" is +// performed. +static ExpDigits SplitToSix(const double value) { + ExpDigits exp_dig; + int exp = 5; + double d = value; + // First step: calculate a close approximation of the output, where the + // value d will be between 100,000 and 999,999, representing the digits + // in the output ASCII array, and exp is the base-10 exponent. It would be + // faster to use a table here, and to look up the base-2 exponent of value, + // however value is an IEEE-754 64-bit number, so the table would have 2,000 + // entries, which is not cache-friendly. + if (d >= 999999.5) { + if (d >= 1e+261) exp += 256, d *= 1e-256; + if (d >= 1e+133) exp += 128, d *= 1e-128; + if (d >= 1e+69) exp += 64, d *= 1e-64; + if (d >= 1e+37) exp += 32, d *= 1e-32; + if (d >= 1e+21) exp += 16, d *= 1e-16; + if (d >= 1e+13) exp += 8, d *= 1e-8; + if (d >= 1e+9) exp += 4, d *= 1e-4; + if (d >= 1e+7) exp += 2, d *= 1e-2; + if (d >= 1e+6) exp += 1, d *= 1e-1; + } else { + if (d < 1e-250) exp -= 256, d *= 1e256; + if (d < 1e-122) exp -= 128, d *= 1e128; + if (d < 1e-58) exp -= 64, d *= 1e64; + if (d < 1e-26) exp -= 32, d *= 1e32; + if (d < 1e-10) exp -= 16, d *= 1e16; + if (d < 1e-2) exp -= 8, d *= 1e8; + if (d < 1e+2) exp -= 4, d *= 1e4; + if (d < 1e+4) exp -= 2, d *= 1e2; + if (d < 1e+5) exp -= 1, d *= 1e1; + } + // At this point, d is in the range [99999.5..999999.5) and exp is in the + // range [-324..308]. Since we need to round d up, we want to add a half + // and truncate. + // However, the technique above may have lost some precision, due to its + // repeated multiplication by constants that each may be off by half a bit + // of precision. This only matters if we're close to the edge though. + // Since we'd like to know if the fractional part of d is close to a half, + // we multiply it by 65536 and see if the fractional part is close to 32768. + // (The number doesn't have to be a power of two,but powers of two are faster) + uint64_t d64k = d * 65536; + int dddddd; // A 6-digit decimal integer. + if ((d64k % 65536) == 32767 || (d64k % 65536) == 32768) { + // OK, it's fairly likely that precision was lost above, which is + // not a surprise given only 52 mantissa bits are available. Therefore + // redo the calculation using 128-bit numbers. (64 bits are not enough). + + // Start out with digits rounded down; maybe add one below. + dddddd = static_cast(d64k / 65536); + + // mantissa is a 64-bit integer representing M.mmm... * 2^63. The actual + // value we're representing, of course, is M.mmm... * 2^exp2. + int exp2; + double m = std::frexp(value, &exp2); + uint64_t mantissa = m * (32768.0 * 65536.0 * 65536.0 * 65536.0); + // std::frexp returns an m value in the range [0.5, 1.0), however we + // can't multiply it by 2^64 and convert to an integer because some FPUs + // throw an exception when converting an number higher than 2^63 into an + // integer - even an unsigned 64-bit integer! Fortunately it doesn't matter + // since m only has 52 significant bits anyway. + mantissa <<= 1; + exp2 -= 64; // not needed, but nice for debugging + + // OK, we are here to compare: + // (dddddd + 0.5) * 10^(exp-5) vs. mantissa * 2^exp2 + // so we can round up dddddd if appropriate. Those values span the full + // range of 600 orders of magnitude of IEE 64-bit floating-point. + // Fortunately, we already know they are very close, so we don't need to + // track the base-2 exponent of both sides. This greatly simplifies the + // the math since the 2^exp2 calculation is unnecessary and the power-of-10 + // calculation can become a power-of-5 instead. + + std::pair edge, val; + if (exp >= 6) { + // Compare (dddddd + 0.5) * 5 ^ (exp - 5) to mantissa + // Since we're tossing powers of two, 2 * dddddd + 1 is the + // same as dddddd + 0.5 + edge = PowFive(2 * dddddd + 1, exp - 5); + + val.first = mantissa; + val.second = 0; + } else { + // We can't compare (dddddd + 0.5) * 5 ^ (exp - 5) to mantissa as we did + // above because (exp - 5) is negative. So we compare (dddddd + 0.5) to + // mantissa * 5 ^ (5 - exp) + edge = PowFive(2 * dddddd + 1, 0); + + val = PowFive(mantissa, 5 - exp); + } + // printf("exp=%d %016lx %016lx vs %016lx %016lx\n", exp, val.first, + // val.second, edge.first, edge.second); + if (val > edge) { + dddddd++; + } else if (val == edge) { + dddddd += (dddddd & 1); + } + } else { + // Here, we are not close to the edge. + dddddd = static_cast((d64k + 32768) / 65536); + } + if (dddddd == 1000000) { + dddddd = 100000; + exp += 1; + } + exp_dig.exponent = exp; + + int two_digits = dddddd / 10000; + dddddd -= two_digits * 10000; + PutTwoDigits(two_digits, &exp_dig.digits[0]); + + two_digits = dddddd / 100; + dddddd -= two_digits * 100; + PutTwoDigits(two_digits, &exp_dig.digits[2]); + + PutTwoDigits(dddddd, &exp_dig.digits[4]); + return exp_dig; +} + +// Helper function for fast formatting of floating-point. +// The result is the same as "%g", a.k.a. "%.6g". +size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { + static_assert(std::numeric_limits::is_iec559, + "IEEE-754/IEC-559 support only"); + + char* out = buffer; // we write data to out, incrementing as we go, but + // FloatToBuffer always returns the address of the buffer + // passed in. + + if (std::isnan(d)) { + strcpy(out, "nan"); // NOLINT(runtime/printf) + return 3; + } + if (d == 0) { // +0 and -0 are handled here + if (std::signbit(d)) *out++ = '-'; + *out++ = '0'; + *out = 0; + return out - buffer; + } + if (d < 0) { + *out++ = '-'; + d = -d; + } + if (std::isinf(d)) { + strcpy(out, "inf"); // NOLINT(runtime/printf) + return out + 3 - buffer; + } + + auto exp_dig = SplitToSix(d); + int exp = exp_dig.exponent; + const char* digits = exp_dig.digits; + out[0] = '0'; + out[1] = '.'; + switch (exp) { + case 5: + memcpy(out, &digits[0], 6), out += 6; + *out = 0; + return out - buffer; + case 4: + memcpy(out, &digits[0], 5), out += 5; + if (digits[5] != '0') { + *out++ = '.'; + *out++ = digits[5]; + } + *out = 0; + return out - buffer; + case 3: + memcpy(out, &digits[0], 4), out += 4; + if ((digits[5] | digits[4]) != '0') { + *out++ = '.'; + *out++ = digits[4]; + if (digits[5] != '0') *out++ = digits[5]; + } + *out = 0; + return out - buffer; + case 2: + memcpy(out, &digits[0], 3), out += 3; + *out++ = '.'; + memcpy(out, &digits[3], 3); + out += 3; + while (out[-1] == '0') --out; + if (out[-1] == '.') --out; + *out = 0; + return out - buffer; + case 1: + memcpy(out, &digits[0], 2), out += 2; + *out++ = '.'; + memcpy(out, &digits[2], 4); + out += 4; + while (out[-1] == '0') --out; + if (out[-1] == '.') --out; + *out = 0; + return out - buffer; + case 0: + memcpy(out, &digits[0], 1), out += 1; + *out++ = '.'; + memcpy(out, &digits[1], 5); + out += 5; + while (out[-1] == '0') --out; + if (out[-1] == '.') --out; + *out = 0; + return out - buffer; + case -4: + out[2] = '0'; + ++out; + ABSL_FALLTHROUGH_INTENDED; + case -3: + out[2] = '0'; + ++out; + ABSL_FALLTHROUGH_INTENDED; + case -2: + out[2] = '0'; + ++out; + ABSL_FALLTHROUGH_INTENDED; + case -1: + out += 2; + memcpy(out, &digits[0], 6); + out += 6; + while (out[-1] == '0') --out; + *out = 0; + return out - buffer; + } + assert(exp < -4 || exp >= 6); + out[0] = digits[0]; + assert(out[1] == '.'); + out += 2; + memcpy(out, &digits[1], 5), out += 5; + while (out[-1] == '0') --out; + if (out[-1] == '.') --out; + *out++ = 'e'; + if (exp > 0) { + *out++ = '+'; + } else { + *out++ = '-'; + exp = -exp; + } + if (exp > 99) { + int dig1 = exp / 100; + exp -= dig1 * 100; + *out++ = '0' + dig1; + } + PutTwoDigits(exp, out); + out += 2; + *out = 0; + return out - buffer; +} + +namespace { +// Represents integer values of digits. +// Uses 36 to indicate an invalid character since we support +// bases up to 36. +static const int8_t kAsciiToInt[256] = { + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, // 16 36s. + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 0, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 36, 36, 36, 36, 36, 36, 36, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 36, 36, 36, 36, 36, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36}; + +// Parse the sign and optional hex or oct prefix in text. +inline bool safe_parse_sign_and_base(absl::string_view* text /*inout*/, + int* base_ptr /*inout*/, + bool* negative_ptr /*output*/) { + if (text->data() == nullptr) { + return false; + } + + const char* start = text->data(); + const char* end = start + text->size(); + int base = *base_ptr; + + // Consume whitespace. + while (start < end && absl::ascii_isspace(start[0])) { + ++start; + } + while (start < end && absl::ascii_isspace(end[-1])) { + --end; + } + if (start >= end) { + return false; + } + + // Consume sign. + *negative_ptr = (start[0] == '-'); + if (*negative_ptr || start[0] == '+') { + ++start; + if (start >= end) { + return false; + } + } + + // Consume base-dependent prefix. + // base 0: "0x" -> base 16, "0" -> base 8, default -> base 10 + // base 16: "0x" -> base 16 + // Also validate the base. + if (base == 0) { + if (end - start >= 2 && start[0] == '0' && + (start[1] == 'x' || start[1] == 'X')) { + base = 16; + start += 2; + if (start >= end) { + // "0x" with no digits after is invalid. + return false; + } + } else if (end - start >= 1 && start[0] == '0') { + base = 8; + start += 1; + } else { + base = 10; + } + } else if (base == 16) { + if (end - start >= 2 && start[0] == '0' && + (start[1] == 'x' || start[1] == 'X')) { + start += 2; + if (start >= end) { + // "0x" with no digits after is invalid. + return false; + } + } + } else if (base >= 2 && base <= 36) { + // okay + } else { + return false; + } + *text = absl::string_view(start, end - start); + *base_ptr = base; + return true; +} + +// Consume digits. +// +// The classic loop: +// +// for each digit +// value = value * base + digit +// value *= sign +// +// The classic loop needs overflow checking. It also fails on the most +// negative integer, -2147483648 in 32-bit two's complement representation. +// +// My improved loop: +// +// if (!negative) +// for each digit +// value = value * base +// value = value + digit +// else +// for each digit +// value = value * base +// value = value - digit +// +// Overflow checking becomes simple. + +// Lookup tables per IntType: +// vmax/base and vmin/base are precomputed because division costs at least 8ns. +// TODO(junyer): Doing this per base instead (i.e. an array of structs, not a +// struct of arrays) would probably be better in terms of d-cache for the most +// commonly used bases. +template +struct LookupTables { + static const IntType kVmaxOverBase[]; + static const IntType kVminOverBase[]; +}; + +// An array initializer macro for X/base where base in [0, 36]. +// However, note that lookups for base in [0, 1] should never happen because +// base has been validated to be in [2, 36] by safe_parse_sign_and_base(). +#define X_OVER_BASE_INITIALIZER(X) \ + { \ + 0, 0, X / 2, X / 3, X / 4, X / 5, X / 6, X / 7, X / 8, X / 9, X / 10, \ + X / 11, X / 12, X / 13, X / 14, X / 15, X / 16, X / 17, X / 18, \ + X / 19, X / 20, X / 21, X / 22, X / 23, X / 24, X / 25, X / 26, \ + X / 27, X / 28, X / 29, X / 30, X / 31, X / 32, X / 33, X / 34, \ + X / 35, X / 36, \ + } + +template +const IntType LookupTables::kVmaxOverBase[] = + X_OVER_BASE_INITIALIZER(std::numeric_limits::max()); + +template +const IntType LookupTables::kVminOverBase[] = + X_OVER_BASE_INITIALIZER(std::numeric_limits::min()); + +#undef X_OVER_BASE_INITIALIZER + +template +inline bool safe_parse_positive_int(absl::string_view text, int base, + IntType* value_p) { + IntType value = 0; + const IntType vmax = std::numeric_limits::max(); + assert(vmax > 0); + assert(base >= 0); + assert(vmax >= static_cast(base)); + const IntType vmax_over_base = LookupTables::kVmaxOverBase[base]; + const char* start = text.data(); + const char* end = start + text.size(); + // loop over digits + for (; start < end; ++start) { + unsigned char c = static_cast(start[0]); + int digit = kAsciiToInt[c]; + if (digit >= base) { + *value_p = value; + return false; + } + if (value > vmax_over_base) { + *value_p = vmax; + return false; + } + value *= base; + if (value > vmax - digit) { + *value_p = vmax; + return false; + } + value += digit; + } + *value_p = value; + return true; +} + +template +inline bool safe_parse_negative_int(absl::string_view text, int base, + IntType* value_p) { + IntType value = 0; + const IntType vmin = std::numeric_limits::min(); + assert(vmin < 0); + assert(vmin <= 0 - base); + IntType vmin_over_base = LookupTables::kVminOverBase[base]; + // 2003 c++ standard [expr.mul] + // "... the sign of the remainder is implementation-defined." + // Although (vmin/base)*base + vmin%base is always vmin. + // 2011 c++ standard tightens the spec but we cannot rely on it. + // TODO(junyer): Handle this in the lookup table generation. + if (vmin % base > 0) { + vmin_over_base += 1; + } + const char* start = text.data(); + const char* end = start + text.size(); + // loop over digits + for (; start < end; ++start) { + unsigned char c = static_cast(start[0]); + int digit = kAsciiToInt[c]; + if (digit >= base) { + *value_p = value; + return false; + } + if (value < vmin_over_base) { + *value_p = vmin; + return false; + } + value *= base; + if (value < vmin + digit) { + *value_p = vmin; + return false; + } + value -= digit; + } + *value_p = value; + return true; +} + +// Input format based on POSIX.1-2008 strtol +// http://pubs.opengroup.org/onlinepubs/9699919799/functions/strtol.html +template +inline bool safe_int_internal(absl::string_view text, IntType* value_p, + int base) { + *value_p = 0; + bool negative; + if (!safe_parse_sign_and_base(&text, &base, &negative)) { + return false; + } + if (!negative) { + return safe_parse_positive_int(text, base, value_p); + } else { + return safe_parse_negative_int(text, base, value_p); + } +} + +template +inline bool safe_uint_internal(absl::string_view text, IntType* value_p, + int base) { + *value_p = 0; + bool negative; + if (!safe_parse_sign_and_base(&text, &base, &negative) || negative) { + return false; + } + return safe_parse_positive_int(text, base, value_p); +} +} // anonymous namespace + +namespace numbers_internal { +bool safe_strto32_base(absl::string_view text, int32_t* value, int base) { + return safe_int_internal(text, value, base); +} + +bool safe_strto64_base(absl::string_view text, int64_t* value, int base) { + return safe_int_internal(text, value, base); +} + +bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base) { + return safe_uint_internal(text, value, base); +} + +bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base) { + return safe_uint_internal(text, value, base); +} +} // namespace numbers_internal + +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/strings/numbers.h b/Firestore/third_party/abseil-cpp/absl/strings/numbers.h new file mode 100644 index 00000000000..adf706a4e26 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/numbers.h @@ -0,0 +1,172 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: numbers.h +// ----------------------------------------------------------------------------- +// +// This package contains functions for converting strings to numbers. For +// converting numbers to strings, use `StrCat()` or `StrAppend()` in str_cat.h, +// which automatically detect and convert most number values appropriately. + +#ifndef ABSL_STRINGS_NUMBERS_H_ +#define ABSL_STRINGS_NUMBERS_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/base/macros.h" +#include "absl/base/port.h" +#include "absl/numeric/int128.h" +#include "absl/strings/string_view.h" + +namespace absl { + +// SimpleAtoi() +// +// Converts the given std::string into an integer value, returning `true` if +// successful. The std::string must reflect a base-10 integer (optionally followed or +// preceded by ASCII whitespace) whose value falls within the range of the +// integer type, +template +ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out); + +// SimpleAtof() +// +// Converts the given std::string (optionally followed or preceded by ASCII +// whitespace) into a float, which may be rounded on overflow or underflow. +ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* value); + +// SimpleAtod() +// +// Converts the given std::string (optionally followed or preceded by ASCII +// whitespace) into a double, which may be rounded on overflow or underflow. +ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* value); + +// SimpleAtob() +// +// Converts the given std::string into a boolean, returning `true` if successful. +// The following case-insensitive strings are interpreted as boolean `true`: +// "true", "t", "yes", "y", "1". The following case-insensitive strings +// are interpreted as boolean `false`: "false", "f", "no", "n", "0". +ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* value); + +} // namespace absl + +// End of public API. Implementation details follow. + +namespace absl { +namespace numbers_internal { + +// safe_strto?() functions for implementing SimpleAtoi() +bool safe_strto32_base(absl::string_view text, int32_t* value, int base); +bool safe_strto64_base(absl::string_view text, int64_t* value, int base); +bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base); +bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base); + +static const int kFastToBufferSize = 32; +static const int kSixDigitsToBufferSize = 16; + +// Helper function for fast formatting of floating-point values. +// The result is the same as printf's "%g", a.k.a. "%.6g"; that is, six +// significant digits are returned, trailing zeros are removed, and numbers +// outside the range 0.0001-999999 are output using scientific notation +// (1.23456e+06). This routine is heavily optimized. +// Required buffer size is `kSixDigitsToBufferSize`. +size_t SixDigitsToBuffer(double d, char* buffer); + +// These functions are intended for speed. All functions take an output buffer +// as an argument and return a pointer to the last byte they wrote, which is the +// terminating '\0'. At most `kFastToBufferSize` bytes are written. +char* FastIntToBuffer(int32_t, char*); +char* FastIntToBuffer(uint32_t, char*); +char* FastIntToBuffer(int64_t, char*); +char* FastIntToBuffer(uint64_t, char*); + +// For enums and integer types that are not an exact match for the types above, +// use templates to call the appropriate one of the four overloads above. +template +char* FastIntToBuffer(int_type i, char* buffer) { + static_assert(sizeof(i) <= 64 / 8, + "FastIntToBuffer works only with 64-bit-or-less integers."); + // TODO(jorg): This signed-ness check is used because it works correctly + // with enums, and it also serves to check that int_type is not a pointer. + // If one day something like std::is_signed works, switch to it. + if (static_cast(1) - 2 < 0) { // Signed + if (sizeof(i) > 32 / 8) { // 33-bit to 64-bit + return FastIntToBuffer(static_cast(i), buffer); + } else { // 32-bit or less + return FastIntToBuffer(static_cast(i), buffer); + } + } else { // Unsigned + if (sizeof(i) > 32 / 8) { // 33-bit to 64-bit + return FastIntToBuffer(static_cast(i), buffer); + } else { // 32-bit or less + return FastIntToBuffer(static_cast(i), buffer); + } + } +} + +} // namespace numbers_internal + +// SimpleAtoi() +// +// Converts a std::string to an integer, using `safe_strto?()` functions for actual +// parsing, returning `true` if successful. The `safe_strto?()` functions apply +// strict checking; the std::string must be a base-10 integer, optionally followed or +// preceded by ASCII whitespace, with a value in the range of the corresponding +// integer type. +template +ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view s, int_type* out) { + static_assert(sizeof(*out) == 4 || sizeof(*out) == 8, + "SimpleAtoi works only with 32-bit or 64-bit integers."); + static_assert(!std::is_floating_point::value, + "Use SimpleAtof or SimpleAtod instead."); + bool parsed; + // TODO(jorg): This signed-ness check is used because it works correctly + // with enums, and it also serves to check that int_type is not a pointer. + // If one day something like std::is_signed works, switch to it. + if (static_cast(1) - 2 < 0) { // Signed + if (sizeof(*out) == 64 / 8) { // 64-bit + int64_t val; + parsed = numbers_internal::safe_strto64_base(s, &val, 10); + *out = static_cast(val); + } else { // 32-bit + int32_t val; + parsed = numbers_internal::safe_strto32_base(s, &val, 10); + *out = static_cast(val); + } + } else { // Unsigned + if (sizeof(*out) == 64 / 8) { // 64-bit + uint64_t val; + parsed = numbers_internal::safe_strtou64_base(s, &val, 10); + *out = static_cast(val); + } else { // 32-bit + uint32_t val; + parsed = numbers_internal::safe_strtou32_base(s, &val, 10); + *out = static_cast(val); + } + } + return parsed; +} + +} // namespace absl + +#endif // ABSL_STRINGS_NUMBERS_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/numbers_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/numbers_test.cc new file mode 100644 index 00000000000..5bb39ca9b5a --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/numbers_test.cc @@ -0,0 +1,1186 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file tests std::string processing functions related to numeric values. + +#include "absl/strings/numbers.h" + +#include +#include // NOLINT(build/c++11) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/str_cat.h" + +#include "absl/strings/internal/numbers_test_common.inc" + +namespace { + +using absl::numbers_internal::kSixDigitsToBufferSize; +using absl::numbers_internal::safe_strto32_base; +using absl::numbers_internal::safe_strto64_base; +using absl::numbers_internal::safe_strtou32_base; +using absl::numbers_internal::safe_strtou64_base; +using absl::numbers_internal::SixDigitsToBuffer; +using absl::SimpleAtoi; +using testing::Eq; +using testing::MatchesRegex; + +// Number of floats to test with. +// 10,000,000 is a reasonable default for a test that only takes a few seconds. +// 1,000,000,000+ triggers checking for all possible mantissa values for +// double-precision tests. 2,000,000,000+ triggers checking for every possible +// single-precision float. +#ifdef _MSC_VER +// Use a smaller number on MSVC to avoid test time out (1 min) +const int kFloatNumCases = 5000000; +#else +const int kFloatNumCases = 10000000; +#endif + +// This is a slow, brute-force routine to compute the exact base-10 +// representation of a double-precision floating-point number. It +// is useful for debugging only. +std::string PerfectDtoa(double d) { + if (d == 0) return "0"; + if (d < 0) return "-" + PerfectDtoa(-d); + + // Basic theory: decompose d into mantissa and exp, where + // d = mantissa * 2^exp, and exp is as close to zero as possible. + int64_t mantissa, exp = 0; + while (d >= 1ULL << 63) ++exp, d *= 0.5; + while ((mantissa = d) != d) --exp, d *= 2.0; + + // Then convert mantissa to ASCII, and either double it (if + // exp > 0) or halve it (if exp < 0) repeatedly. "halve it" + // in this case means multiplying it by five and dividing by 10. + constexpr int maxlen = 1100; // worst case is actually 1030 or so. + char buf[maxlen + 5]; + for (int64_t num = mantissa, pos = maxlen; --pos >= 0;) { + buf[pos] = '0' + (num % 10); + num /= 10; + } + char* begin = &buf[0]; + char* end = buf + maxlen; + for (int i = 0; i != exp; i += (exp > 0) ? 1 : -1) { + int carry = 0; + for (char* p = end; --p != begin;) { + int dig = *p - '0'; + dig = dig * (exp > 0 ? 2 : 5) + carry; + carry = dig / 10; + dig %= 10; + *p = '0' + dig; + } + } + if (exp < 0) { + // "dividing by 10" above means we have to add the decimal point. + memmove(end + 1 + exp, end + exp, 1 - exp); + end[exp] = '.'; + ++end; + } + while (*begin == '0' && begin[1] != '.') ++begin; + return {begin, end}; +} + +TEST(ToString, PerfectDtoa) { + EXPECT_THAT(PerfectDtoa(1), Eq("1")); + EXPECT_THAT(PerfectDtoa(0.1), + Eq("0.1000000000000000055511151231257827021181583404541015625")); + EXPECT_THAT(PerfectDtoa(1e24), Eq("999999999999999983222784")); + EXPECT_THAT(PerfectDtoa(5e-324), MatchesRegex("0.0000.*625")); + for (int i = 0; i < 100; ++i) { + for (double multiplier : + {1e-300, 1e-200, 1e-100, 0.1, 1.0, 10.0, 1e100, 1e300}) { + double d = multiplier * i; + std::string s = PerfectDtoa(d); + EXPECT_EQ(d, strtod(s.c_str(), nullptr)); + } + } +} + +template +struct MyInteger { + integer i; + explicit constexpr MyInteger(integer i) : i(i) {} + constexpr operator integer() const { return i; } + + constexpr MyInteger operator+(MyInteger other) const { return i + other.i; } + constexpr MyInteger operator-(MyInteger other) const { return i - other.i; } + constexpr MyInteger operator*(MyInteger other) const { return i * other.i; } + constexpr MyInteger operator/(MyInteger other) const { return i / other.i; } + + constexpr bool operator<(MyInteger other) const { return i < other.i; } + constexpr bool operator<=(MyInteger other) const { return i <= other.i; } + constexpr bool operator==(MyInteger other) const { return i == other.i; } + constexpr bool operator>=(MyInteger other) const { return i >= other.i; } + constexpr bool operator>(MyInteger other) const { return i > other.i; } + constexpr bool operator!=(MyInteger other) const { return i != other.i; } + + integer as_integer() const { return i; } +}; + +typedef MyInteger MyInt64; +typedef MyInteger MyUInt64; + +void CheckInt32(int32_t x) { + char buffer[absl::numbers_internal::kFastToBufferSize]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + std::string expected = std::to_string(x); + EXPECT_EQ(expected, std::string(buffer, actual)) << " Input " << x; + + char* generic_actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + EXPECT_EQ(expected, std::string(buffer, generic_actual)) << " Input " << x; +} + +void CheckInt64(int64_t x) { + char buffer[absl::numbers_internal::kFastToBufferSize + 3]; + buffer[0] = '*'; + buffer[23] = '*'; + buffer[24] = '*'; + char* actual = absl::numbers_internal::FastIntToBuffer(x, &buffer[1]); + std::string expected = std::to_string(x); + EXPECT_EQ(expected, std::string(&buffer[1], actual)) << " Input " << x; + EXPECT_EQ(buffer[0], '*'); + EXPECT_EQ(buffer[23], '*'); + EXPECT_EQ(buffer[24], '*'); + + char* my_actual = + absl::numbers_internal::FastIntToBuffer(MyInt64(x), &buffer[1]); + EXPECT_EQ(expected, std::string(&buffer[1], my_actual)) << " Input " << x; +} + +void CheckUInt32(uint32_t x) { + char buffer[absl::numbers_internal::kFastToBufferSize]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + std::string expected = std::to_string(x); + EXPECT_EQ(expected, std::string(buffer, actual)) << " Input " << x; + + char* generic_actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + EXPECT_EQ(expected, std::string(buffer, generic_actual)) << " Input " << x; +} + +void CheckUInt64(uint64_t x) { + char buffer[absl::numbers_internal::kFastToBufferSize + 1]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, &buffer[1]); + std::string expected = std::to_string(x); + EXPECT_EQ(expected, std::string(&buffer[1], actual)) << " Input " << x; + + char* generic_actual = absl::numbers_internal::FastIntToBuffer(x, &buffer[1]); + EXPECT_EQ(expected, std::string(&buffer[1], generic_actual)) << " Input " << x; + + char* my_actual = + absl::numbers_internal::FastIntToBuffer(MyUInt64(x), &buffer[1]); + EXPECT_EQ(expected, std::string(&buffer[1], my_actual)) << " Input " << x; +} + +void CheckHex64(uint64_t v) { + char expected[16 + 1]; + std::string actual = absl::StrCat(absl::Hex(v, absl::kZeroPad16)); + snprintf(expected, sizeof(expected), "%016" PRIx64, static_cast(v)); + EXPECT_EQ(expected, actual) << " Input " << v; +} + +TEST(Numbers, TestFastPrints) { + for (int i = -100; i <= 100; i++) { + CheckInt32(i); + CheckInt64(i); + } + for (int i = 0; i <= 100; i++) { + CheckUInt32(i); + CheckUInt64(i); + } + // Test min int to make sure that works + CheckInt32(INT_MIN); + CheckInt32(INT_MAX); + CheckInt64(LONG_MIN); + CheckInt64(uint64_t{1000000000}); + CheckInt64(uint64_t{9999999999}); + CheckInt64(uint64_t{100000000000000}); + CheckInt64(uint64_t{999999999999999}); + CheckInt64(uint64_t{1000000000000000000}); + CheckInt64(uint64_t{1199999999999999999}); + CheckInt64(int64_t{-700000000000000000}); + CheckInt64(LONG_MAX); + CheckUInt32(std::numeric_limits::max()); + CheckUInt64(uint64_t{1000000000}); + CheckUInt64(uint64_t{9999999999}); + CheckUInt64(uint64_t{100000000000000}); + CheckUInt64(uint64_t{999999999999999}); + CheckUInt64(uint64_t{1000000000000000000}); + CheckUInt64(uint64_t{1199999999999999999}); + CheckUInt64(std::numeric_limits::max()); + + for (int i = 0; i < 10000; i++) { + CheckHex64(i); + } + CheckHex64(uint64_t{0x123456789abcdef0}); +} + +template +void VerifySimpleAtoiGood(in_val_type in_value, int_type exp_value) { + std::string s = absl::StrCat(in_value); + int_type x = static_cast(~exp_value); + EXPECT_TRUE(SimpleAtoi(s, &x)) + << "in_value=" << in_value << " s=" << s << " x=" << x; + EXPECT_EQ(exp_value, x); + x = static_cast(~exp_value); + EXPECT_TRUE(SimpleAtoi(s.c_str(), &x)); + EXPECT_EQ(exp_value, x); +} + +template +void VerifySimpleAtoiBad(in_val_type in_value) { + std::string s = absl::StrCat(in_value); + int_type x; + EXPECT_FALSE(SimpleAtoi(s, &x)); + EXPECT_FALSE(SimpleAtoi(s.c_str(), &x)); +} + +TEST(NumbersTest, Atoi) { + // SimpleAtoi(absl::string_view, int32_t) + VerifySimpleAtoiGood(0, 0); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(-42, -42); + + VerifySimpleAtoiGood(std::numeric_limits::min(), + std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + + // SimpleAtoi(absl::string_view, uint32_t) + VerifySimpleAtoiGood(0, 0); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiBad(-42); + + VerifySimpleAtoiBad(std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiBad(std::numeric_limits::min()); + VerifySimpleAtoiBad(std::numeric_limits::max()); + VerifySimpleAtoiBad(std::numeric_limits::max()); + + // SimpleAtoi(absl::string_view, int64_t) + VerifySimpleAtoiGood(0, 0); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(-42, -42); + + VerifySimpleAtoiGood(std::numeric_limits::min(), + std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::min(), + std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiBad(std::numeric_limits::max()); + + // SimpleAtoi(absl::string_view, uint64_t) + VerifySimpleAtoiGood(0, 0); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiBad(-42); + + VerifySimpleAtoiBad(std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiBad(std::numeric_limits::min()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + VerifySimpleAtoiGood(std::numeric_limits::max(), + std::numeric_limits::max()); + + // Some other types + VerifySimpleAtoiGood(-42, -42); + VerifySimpleAtoiGood(-42, -42); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(-42, -42); + VerifySimpleAtoiGood(-42, -42); // NOLINT(runtime/int) + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(42, 42); + VerifySimpleAtoiGood(42, 42); +} + +TEST(NumbersTest, Atoenum) { + enum E01 { + E01_zero = 0, + E01_one = 1, + }; + + VerifySimpleAtoiGood(E01_zero, E01_zero); + VerifySimpleAtoiGood(E01_one, E01_one); + + enum E_101 { + E_101_minusone = -1, + E_101_zero = 0, + E_101_one = 1, + }; + + VerifySimpleAtoiGood(E_101_minusone, E_101_minusone); + VerifySimpleAtoiGood(E_101_zero, E_101_zero); + VerifySimpleAtoiGood(E_101_one, E_101_one); + + enum E_bigint { + E_bigint_zero = 0, + E_bigint_one = 1, + E_bigint_max31 = static_cast(0x7FFFFFFF), + }; + + VerifySimpleAtoiGood(E_bigint_zero, E_bigint_zero); + VerifySimpleAtoiGood(E_bigint_one, E_bigint_one); + VerifySimpleAtoiGood(E_bigint_max31, E_bigint_max31); + + enum E_fullint { + E_fullint_zero = 0, + E_fullint_one = 1, + E_fullint_max31 = static_cast(0x7FFFFFFF), + E_fullint_min32 = INT32_MIN, + }; + + VerifySimpleAtoiGood(E_fullint_zero, E_fullint_zero); + VerifySimpleAtoiGood(E_fullint_one, E_fullint_one); + VerifySimpleAtoiGood(E_fullint_max31, E_fullint_max31); + VerifySimpleAtoiGood(E_fullint_min32, E_fullint_min32); + + enum E_biguint { + E_biguint_zero = 0, + E_biguint_one = 1, + E_biguint_max31 = static_cast(0x7FFFFFFF), + E_biguint_max32 = static_cast(0xFFFFFFFF), + }; + + VerifySimpleAtoiGood(E_biguint_zero, E_biguint_zero); + VerifySimpleAtoiGood(E_biguint_one, E_biguint_one); + VerifySimpleAtoiGood(E_biguint_max31, E_biguint_max31); + VerifySimpleAtoiGood(E_biguint_max32, E_biguint_max32); +} + +TEST(stringtest, safe_strto32_base) { + int32_t value; + EXPECT_TRUE(safe_strto32_base("0x34234324", &value, 16)); + EXPECT_EQ(0x34234324, value); + + EXPECT_TRUE(safe_strto32_base("0X34234324", &value, 16)); + EXPECT_EQ(0x34234324, value); + + EXPECT_TRUE(safe_strto32_base("34234324", &value, 16)); + EXPECT_EQ(0x34234324, value); + + EXPECT_TRUE(safe_strto32_base("0", &value, 16)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto32_base(" \t\n -0x34234324", &value, 16)); + EXPECT_EQ(-0x34234324, value); + + EXPECT_TRUE(safe_strto32_base(" \t\n -34234324", &value, 16)); + EXPECT_EQ(-0x34234324, value); + + EXPECT_TRUE(safe_strto32_base("7654321", &value, 8)); + EXPECT_EQ(07654321, value); + + EXPECT_TRUE(safe_strto32_base("-01234", &value, 8)); + EXPECT_EQ(-01234, value); + + EXPECT_FALSE(safe_strto32_base("1834", &value, 8)); + + // Autodetect base. + EXPECT_TRUE(safe_strto32_base("0", &value, 0)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto32_base("077", &value, 0)); + EXPECT_EQ(077, value); // Octal interpretation + + // Leading zero indicates octal, but then followed by invalid digit. + EXPECT_FALSE(safe_strto32_base("088", &value, 0)); + + // Leading 0x indicated hex, but then followed by invalid digit. + EXPECT_FALSE(safe_strto32_base("0xG", &value, 0)); + + // Base-10 version. + EXPECT_TRUE(safe_strto32_base("34234324", &value, 10)); + EXPECT_EQ(34234324, value); + + EXPECT_TRUE(safe_strto32_base("0", &value, 10)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto32_base(" \t\n -34234324", &value, 10)); + EXPECT_EQ(-34234324, value); + + EXPECT_TRUE(safe_strto32_base("34234324 \n\t ", &value, 10)); + EXPECT_EQ(34234324, value); + + // Invalid ints. + EXPECT_FALSE(safe_strto32_base("", &value, 10)); + EXPECT_FALSE(safe_strto32_base(" ", &value, 10)); + EXPECT_FALSE(safe_strto32_base("abc", &value, 10)); + EXPECT_FALSE(safe_strto32_base("34234324a", &value, 10)); + EXPECT_FALSE(safe_strto32_base("34234.3", &value, 10)); + + // Out of bounds. + EXPECT_FALSE(safe_strto32_base("2147483648", &value, 10)); + EXPECT_FALSE(safe_strto32_base("-2147483649", &value, 10)); + + // String version. + EXPECT_TRUE(safe_strto32_base(std::string("0x1234"), &value, 16)); + EXPECT_EQ(0x1234, value); + + // Base-10 std::string version. + EXPECT_TRUE(safe_strto32_base("1234", &value, 10)); + EXPECT_EQ(1234, value); +} + +TEST(stringtest, safe_strto32_range) { + // These tests verify underflow/overflow behaviour. + int32_t value; + EXPECT_FALSE(safe_strto32_base("2147483648", &value, 10)); + EXPECT_EQ(std::numeric_limits::max(), value); + + EXPECT_TRUE(safe_strto32_base("-2147483648", &value, 10)); + EXPECT_EQ(std::numeric_limits::min(), value); + + EXPECT_FALSE(safe_strto32_base("-2147483649", &value, 10)); + EXPECT_EQ(std::numeric_limits::min(), value); +} + +TEST(stringtest, safe_strto64_range) { + // These tests verify underflow/overflow behaviour. + int64_t value; + EXPECT_FALSE(safe_strto64_base("9223372036854775808", &value, 10)); + EXPECT_EQ(std::numeric_limits::max(), value); + + EXPECT_TRUE(safe_strto64_base("-9223372036854775808", &value, 10)); + EXPECT_EQ(std::numeric_limits::min(), value); + + EXPECT_FALSE(safe_strto64_base("-9223372036854775809", &value, 10)); + EXPECT_EQ(std::numeric_limits::min(), value); +} + +TEST(stringtest, safe_strto32_leading_substring) { + // These tests verify this comment in numbers.h: + // On error, returns false, and sets *value to: [...] + // conversion of leading substring if available ("123@@@" -> 123) + // 0 if no leading substring available + int32_t value; + EXPECT_FALSE(safe_strto32_base("04069@@@", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto32_base("04069@@@", &value, 8)); + EXPECT_EQ(0406, value); + + EXPECT_FALSE(safe_strto32_base("04069balloons", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto32_base("04069balloons", &value, 16)); + EXPECT_EQ(0x4069ba, value); + + EXPECT_FALSE(safe_strto32_base("@@@", &value, 10)); + EXPECT_EQ(0, value); // there was no leading substring +} + +TEST(stringtest, safe_strto64_leading_substring) { + // These tests verify this comment in numbers.h: + // On error, returns false, and sets *value to: [...] + // conversion of leading substring if available ("123@@@" -> 123) + // 0 if no leading substring available + int64_t value; + EXPECT_FALSE(safe_strto64_base("04069@@@", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto64_base("04069@@@", &value, 8)); + EXPECT_EQ(0406, value); + + EXPECT_FALSE(safe_strto64_base("04069balloons", &value, 10)); + EXPECT_EQ(4069, value); + + EXPECT_FALSE(safe_strto64_base("04069balloons", &value, 16)); + EXPECT_EQ(0x4069ba, value); + + EXPECT_FALSE(safe_strto64_base("@@@", &value, 10)); + EXPECT_EQ(0, value); // there was no leading substring +} + +TEST(stringtest, safe_strto64_base) { + int64_t value; + EXPECT_TRUE(safe_strto64_base("0x3423432448783446", &value, 16)); + EXPECT_EQ(int64_t{0x3423432448783446}, value); + + EXPECT_TRUE(safe_strto64_base("3423432448783446", &value, 16)); + EXPECT_EQ(int64_t{0x3423432448783446}, value); + + EXPECT_TRUE(safe_strto64_base("0", &value, 16)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto64_base(" \t\n -0x3423432448783446", &value, 16)); + EXPECT_EQ(int64_t{-0x3423432448783446}, value); + + EXPECT_TRUE(safe_strto64_base(" \t\n -3423432448783446", &value, 16)); + EXPECT_EQ(int64_t{-0x3423432448783446}, value); + + EXPECT_TRUE(safe_strto64_base("123456701234567012", &value, 8)); + EXPECT_EQ(int64_t{0123456701234567012}, value); + + EXPECT_TRUE(safe_strto64_base("-017777777777777", &value, 8)); + EXPECT_EQ(int64_t{-017777777777777}, value); + + EXPECT_FALSE(safe_strto64_base("19777777777777", &value, 8)); + + // Autodetect base. + EXPECT_TRUE(safe_strto64_base("0", &value, 0)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto64_base("077", &value, 0)); + EXPECT_EQ(077, value); // Octal interpretation + + // Leading zero indicates octal, but then followed by invalid digit. + EXPECT_FALSE(safe_strto64_base("088", &value, 0)); + + // Leading 0x indicated hex, but then followed by invalid digit. + EXPECT_FALSE(safe_strto64_base("0xG", &value, 0)); + + // Base-10 version. + EXPECT_TRUE(safe_strto64_base("34234324487834466", &value, 10)); + EXPECT_EQ(int64_t{34234324487834466}, value); + + EXPECT_TRUE(safe_strto64_base("0", &value, 10)); + EXPECT_EQ(0, value); + + EXPECT_TRUE(safe_strto64_base(" \t\n -34234324487834466", &value, 10)); + EXPECT_EQ(int64_t{-34234324487834466}, value); + + EXPECT_TRUE(safe_strto64_base("34234324487834466 \n\t ", &value, 10)); + EXPECT_EQ(int64_t{34234324487834466}, value); + + // Invalid ints. + EXPECT_FALSE(safe_strto64_base("", &value, 10)); + EXPECT_FALSE(safe_strto64_base(" ", &value, 10)); + EXPECT_FALSE(safe_strto64_base("abc", &value, 10)); + EXPECT_FALSE(safe_strto64_base("34234324487834466a", &value, 10)); + EXPECT_FALSE(safe_strto64_base("34234487834466.3", &value, 10)); + + // Out of bounds. + EXPECT_FALSE(safe_strto64_base("9223372036854775808", &value, 10)); + EXPECT_FALSE(safe_strto64_base("-9223372036854775809", &value, 10)); + + // String version. + EXPECT_TRUE(safe_strto64_base(std::string("0x1234"), &value, 16)); + EXPECT_EQ(0x1234, value); + + // Base-10 std::string version. + EXPECT_TRUE(safe_strto64_base("1234", &value, 10)); + EXPECT_EQ(1234, value); +} + +const size_t kNumRandomTests = 10000; + +template +void test_random_integer_parse_base(bool (*parse_func)(absl::string_view, + IntType* value, + int base)) { + using RandomEngine = std::minstd_rand0; + std::random_device rd; + RandomEngine rng(rd()); + std::uniform_int_distribution random_int( + std::numeric_limits::min()); + std::uniform_int_distribution random_base(2, 35); + for (size_t i = 0; i < kNumRandomTests; i++) { + IntType value = random_int(rng); + int base = random_base(rng); + std::string str_value; + EXPECT_TRUE(Itoa(value, base, &str_value)); + IntType parsed_value; + + // Test successful parse + EXPECT_TRUE(parse_func(str_value, &parsed_value, base)); + EXPECT_EQ(parsed_value, value); + + // Test overflow + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits::max(), value), + &parsed_value, base)); + + // Test underflow + if (std::numeric_limits::min() < 0) { + EXPECT_FALSE( + parse_func(absl::StrCat(std::numeric_limits::min(), value), + &parsed_value, base)); + } else { + EXPECT_FALSE(parse_func(absl::StrCat("-", value), &parsed_value, base)); + } + } +} + +TEST(stringtest, safe_strto32_random) { + test_random_integer_parse_base(&safe_strto32_base); +} +TEST(stringtest, safe_strto64_random) { + test_random_integer_parse_base(&safe_strto64_base); +} +TEST(stringtest, safe_strtou32_random) { + test_random_integer_parse_base(&safe_strtou32_base); +} +TEST(stringtest, safe_strtou64_random) { + test_random_integer_parse_base(&safe_strtou64_base); +} + +TEST(stringtest, safe_strtou32_base) { + for (int i = 0; strtouint32_test_cases[i].str != nullptr; ++i) { + const auto& e = strtouint32_test_cases[i]; + uint32_t value; + EXPECT_EQ(e.expect_ok, safe_strtou32_base(e.str, &value, e.base)) + << "str=\"" << e.str << "\" base=" << e.base; + if (e.expect_ok) { + EXPECT_EQ(e.expected, value) << "i=" << i << " str=\"" << e.str + << "\" base=" << e.base; + } + } +} + +TEST(stringtest, safe_strtou32_base_length_delimited) { + for (int i = 0; strtouint32_test_cases[i].str != nullptr; ++i) { + const auto& e = strtouint32_test_cases[i]; + std::string tmp(e.str); + tmp.append("12"); // Adds garbage at the end. + + uint32_t value; + EXPECT_EQ(e.expect_ok, + safe_strtou32_base(absl::string_view(tmp.data(), strlen(e.str)), + &value, e.base)) + << "str=\"" << e.str << "\" base=" << e.base; + if (e.expect_ok) { + EXPECT_EQ(e.expected, value) << "i=" << i << " str=" << e.str + << " base=" << e.base; + } + } +} + +TEST(stringtest, safe_strtou64_base) { + for (int i = 0; strtouint64_test_cases[i].str != nullptr; ++i) { + const auto& e = strtouint64_test_cases[i]; + uint64_t value; + EXPECT_EQ(e.expect_ok, safe_strtou64_base(e.str, &value, e.base)) + << "str=\"" << e.str << "\" base=" << e.base; + if (e.expect_ok) { + EXPECT_EQ(e.expected, value) << "str=" << e.str << " base=" << e.base; + } + } +} + +TEST(stringtest, safe_strtou64_base_length_delimited) { + for (int i = 0; strtouint64_test_cases[i].str != nullptr; ++i) { + const auto& e = strtouint64_test_cases[i]; + std::string tmp(e.str); + tmp.append("12"); // Adds garbage at the end. + + uint64_t value; + EXPECT_EQ(e.expect_ok, + safe_strtou64_base(absl::string_view(tmp.data(), strlen(e.str)), + &value, e.base)) + << "str=\"" << e.str << "\" base=" << e.base; + if (e.expect_ok) { + EXPECT_EQ(e.expected, value) << "str=\"" << e.str << "\" base=" << e.base; + } + } +} + +// feenableexcept() and fedisableexcept() are missing on Mac OS X, MSVC. +#if defined(_MSC_VER) || defined(__APPLE__) +#define ABSL_MISSING_FEENABLEEXCEPT 1 +#define ABSL_MISSING_FEDISABLEEXCEPT 1 +#endif + +class SimpleDtoaTest : public testing::Test { + protected: + void SetUp() override { + // Store the current floating point env & clear away any pending exceptions. + feholdexcept(&fp_env_); +#ifndef ABSL_MISSING_FEENABLEEXCEPT + // Turn on floating point exceptions. + feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); +#endif + } + + void TearDown() override { + // Restore the floating point environment to the original state. + // In theory fedisableexcept is unnecessary; fesetenv will also do it. + // In practice, our toolchains have subtle bugs. +#ifndef ABSL_MISSING_FEDISABLEEXCEPT + fedisableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); +#endif + fesetenv(&fp_env_); + } + + std::string ToNineDigits(double value) { + char buffer[16]; // more than enough for %.9g + snprintf(buffer, sizeof(buffer), "%.9g", value); + return buffer; + } + + fenv_t fp_env_; +}; + +// Run the given runnable functor for "cases" test cases, chosen over the +// available range of float. pi and e and 1/e are seeded, and then all +// available integer powers of 2 and 10 are multiplied against them. In +// addition to trying all those values, we try the next higher and next lower +// float, and then we add additional test cases evenly distributed between them. +// Each test case is passed to runnable as both a positive and negative value. +template +void ExhaustiveFloat(uint32_t cases, R&& runnable) { + runnable(0.0f); + runnable(-0.0f); + if (cases >= 2e9) { // more than 2 billion? Might as well run them all. + for (float f = 0; f < std::numeric_limits::max(); ) { + f = nextafterf(f, std::numeric_limits::max()); + runnable(-f); + runnable(f); + } + return; + } + std::set floats = {3.4028234e38f}; + for (float f : {1.0, 3.14159265, 2.718281828, 1 / 2.718281828}) { + for (float testf = f; testf != 0; testf *= 0.1f) floats.insert(testf); + for (float testf = f; testf != 0; testf *= 0.5f) floats.insert(testf); + for (float testf = f; testf < 3e38f / 2; testf *= 2.0f) + floats.insert(testf); + for (float testf = f; testf < 3e38f / 10; testf *= 10) floats.insert(testf); + } + + float last = *floats.begin(); + + runnable(last); + runnable(-last); + int iters_per_float = cases / floats.size(); + if (iters_per_float == 0) iters_per_float = 1; + for (float f : floats) { + if (f == last) continue; + float testf = nextafter(last, std::numeric_limits::max()); + runnable(testf); + runnable(-testf); + last = testf; + if (f == last) continue; + double step = (double{f} - last) / iters_per_float; + for (double d = last + step; d < f; d += step) { + testf = d; + if (testf != last) { + runnable(testf); + runnable(-testf); + last = testf; + } + } + testf = nextafter(f, 0.0f); + if (testf > last) { + runnable(testf); + runnable(-testf); + last = testf; + } + if (f != last) { + runnable(f); + runnable(-f); + last = f; + } + } +} + +TEST_F(SimpleDtoaTest, ExhaustiveDoubleToSixDigits) { + uint64_t test_count = 0; + std::vector mismatches; + auto checker = [&](double d) { + if (d != d) return; // rule out NaNs + ++test_count; + char sixdigitsbuf[kSixDigitsToBufferSize] = {0}; + SixDigitsToBuffer(d, sixdigitsbuf); + char snprintfbuf[kSixDigitsToBufferSize] = {0}; + snprintf(snprintfbuf, kSixDigitsToBufferSize, "%g", d); + if (strcmp(sixdigitsbuf, snprintfbuf) != 0) { + mismatches.push_back(d); + if (mismatches.size() < 10) { + ABSL_RAW_LOG(ERROR, "%s", + absl::StrCat("Six-digit failure with double. ", "d=", d, + "=", d, " sixdigits=", sixdigitsbuf, + " printf(%g)=", snprintfbuf) + .c_str()); + } + } + }; + // Some quick sanity checks... + checker(5e-324); + checker(1e-308); + checker(1.0); + checker(1.000005); + checker(1.7976931348623157e308); + checker(0.00390625); +#ifndef _MSC_VER + // on MSVC, snprintf() rounds it to 0.00195313. SixDigitsToBuffer() rounds it + // to 0.00195312 (round half to even). + checker(0.001953125); +#endif + checker(0.005859375); + // Some cases where the rounding is very very close + checker(1.089095e-15); + checker(3.274195e-55); + checker(6.534355e-146); + checker(2.920845e+234); + + if (mismatches.empty()) { + test_count = 0; + ExhaustiveFloat(kFloatNumCases, checker); + + test_count = 0; + std::vector digit_testcases{ + 100000, 100001, 100002, 100005, 100010, 100020, 100050, 100100, // misc + 195312, 195313, // 1.953125 is a case where we round down, just barely. + 200000, 500000, 800000, // misc mid-range cases + 585937, 585938, // 5.859375 is a case where we round up, just barely. + 900000, 990000, 999000, 999900, 999990, 999996, 999997, 999998, 999999}; + if (kFloatNumCases >= 1e9) { + // If at least 1 billion test cases were requested, user wants an + // exhaustive test. So let's test all mantissas, too. + constexpr int min_mantissa = 100000, max_mantissa = 999999; + digit_testcases.resize(max_mantissa - min_mantissa + 1); + std::iota(digit_testcases.begin(), digit_testcases.end(), min_mantissa); + } + + for (int exponent = -324; exponent <= 308; ++exponent) { + double powten = pow(10.0, exponent); + if (powten == 0) powten = 5e-324; + if (kFloatNumCases >= 1e9) { + // The exhaustive test takes a very long time, so log progress. + char buf[kSixDigitsToBufferSize]; + ABSL_RAW_LOG( + INFO, "%s", + absl::StrCat("Exp ", exponent, " powten=", powten, "(", + powten, ") (", + std::string(buf, SixDigitsToBuffer(powten, buf)), ")") + .c_str()); + } + for (int digits : digit_testcases) { + if (exponent == 308 && digits >= 179769) break; // don't overflow! + double digiform = (digits + 0.5) * 0.00001; + double testval = digiform * powten; + double pretestval = nextafter(testval, 0); + double posttestval = nextafter(testval, 1.7976931348623157e308); + checker(testval); + checker(pretestval); + checker(posttestval); + } + } + } else { + EXPECT_EQ(mismatches.size(), 0); + for (size_t i = 0; i < mismatches.size(); ++i) { + if (i > 100) i = mismatches.size() - 1; + double d = mismatches[i]; + char sixdigitsbuf[kSixDigitsToBufferSize] = {0}; + SixDigitsToBuffer(d, sixdigitsbuf); + char snprintfbuf[kSixDigitsToBufferSize] = {0}; + snprintf(snprintfbuf, kSixDigitsToBufferSize, "%g", d); + double before = nextafter(d, 0.0); + double after = nextafter(d, 1.7976931348623157e308); + char b1[32], b2[kSixDigitsToBufferSize]; + ABSL_RAW_LOG( + ERROR, "%s", + absl::StrCat( + "Mismatch #", i, " d=", d, " (", ToNineDigits(d), ")", + " sixdigits='", sixdigitsbuf, "'", " snprintf='", snprintfbuf, + "'", " Before.=", PerfectDtoa(before), " ", + (SixDigitsToBuffer(before, b2), b2), + " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", before), b1), + " Perfect=", PerfectDtoa(d), " ", (SixDigitsToBuffer(d, b2), b2), + " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", d), b1), + " After.=.", PerfectDtoa(after), " ", + (SixDigitsToBuffer(after, b2), b2), + " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", after), b1)) + .c_str()); + } + } +} + +TEST(StrToInt32, Partial) { + struct Int32TestLine { + std::string input; + bool status; + int32_t value; + }; + const int32_t int32_min = std::numeric_limits::min(); + const int32_t int32_max = std::numeric_limits::max(); + Int32TestLine int32_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(int32_min, int32_max), false, int32_min}, + {absl::StrCat(int32_max, int32_max), false, int32_max}, + }; + + for (const Int32TestLine& test_line : int32_test_line) { + int32_t value = -2; + bool status = safe_strto32_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto32_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto32_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToUint32, Partial) { + struct Uint32TestLine { + std::string input; + bool status; + uint32_t value; + }; + const uint32_t uint32_max = std::numeric_limits::max(); + Uint32TestLine uint32_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(uint32_max, uint32_max), false, uint32_max}, + }; + + for (const Uint32TestLine& test_line : uint32_test_line) { + uint32_t value = 2; + bool status = safe_strtou32_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou32_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou32_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToInt64, Partial) { + struct Int64TestLine { + std::string input; + bool status; + int64_t value; + }; + const int64_t int64_min = std::numeric_limits::min(); + const int64_t int64_max = std::numeric_limits::max(); + Int64TestLine int64_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(int64_min, int64_max), false, int64_min}, + {absl::StrCat(int64_max, int64_max), false, int64_max}, + }; + + for (const Int64TestLine& test_line : int64_test_line) { + int64_t value = -2; + bool status = safe_strto64_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto64_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = -2; + status = safe_strto64_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToUint64, Partial) { + struct Uint64TestLine { + std::string input; + bool status; + uint64_t value; + }; + const uint64_t uint64_max = std::numeric_limits::max(); + Uint64TestLine uint64_test_line[] = { + {"", false, 0}, + {" ", false, 0}, + {"-", false, 0}, + {"123@@@", false, 123}, + {absl::StrCat(uint64_max, uint64_max), false, uint64_max}, + }; + + for (const Uint64TestLine& test_line : uint64_test_line) { + uint64_t value = 2; + bool status = safe_strtou64_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou64_base(test_line.input, &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + value = 2; + status = safe_strtou64_base(absl::string_view(test_line.input), &value, 10); + EXPECT_EQ(test_line.status, status) << test_line.input; + EXPECT_EQ(test_line.value, value) << test_line.input; + } +} + +TEST(StrToInt32Base, PrefixOnly) { + struct Int32TestLine { + std::string input; + bool status; + int32_t value; + }; + Int32TestLine int32_test_line[] = { + { "", false, 0 }, + { "-", false, 0 }, + { "-0", true, 0 }, + { "0", true, 0 }, + { "0x", false, 0 }, + { "-0x", false, 0 }, + }; + const int base_array[] = { 0, 2, 8, 10, 16 }; + + for (const Int32TestLine& line : int32_test_line) { + for (const int base : base_array) { + int32_t value = 2; + bool status = safe_strto32_base(line.input.c_str(), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strto32_base(line.input, &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strto32_base(absl::string_view(line.input), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + } + } +} + +TEST(StrToUint32Base, PrefixOnly) { + struct Uint32TestLine { + std::string input; + bool status; + uint32_t value; + }; + Uint32TestLine uint32_test_line[] = { + { "", false, 0 }, + { "0", true, 0 }, + { "0x", false, 0 }, + }; + const int base_array[] = { 0, 2, 8, 10, 16 }; + + for (const Uint32TestLine& line : uint32_test_line) { + for (const int base : base_array) { + uint32_t value = 2; + bool status = safe_strtou32_base(line.input.c_str(), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strtou32_base(line.input, &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strtou32_base(absl::string_view(line.input), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + } + } +} + +TEST(StrToInt64Base, PrefixOnly) { + struct Int64TestLine { + std::string input; + bool status; + int64_t value; + }; + Int64TestLine int64_test_line[] = { + { "", false, 0 }, + { "-", false, 0 }, + { "-0", true, 0 }, + { "0", true, 0 }, + { "0x", false, 0 }, + { "-0x", false, 0 }, + }; + const int base_array[] = { 0, 2, 8, 10, 16 }; + + for (const Int64TestLine& line : int64_test_line) { + for (const int base : base_array) { + int64_t value = 2; + bool status = safe_strto64_base(line.input.c_str(), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strto64_base(line.input, &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strto64_base(absl::string_view(line.input), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + } + } +} + +TEST(StrToUint64Base, PrefixOnly) { + struct Uint64TestLine { + std::string input; + bool status; + uint64_t value; + }; + Uint64TestLine uint64_test_line[] = { + { "", false, 0 }, + { "0", true, 0 }, + { "0x", false, 0 }, + }; + const int base_array[] = { 0, 2, 8, 10, 16 }; + + for (const Uint64TestLine& line : uint64_test_line) { + for (const int base : base_array) { + uint64_t value = 2; + bool status = safe_strtou64_base(line.input.c_str(), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strtou64_base(line.input, &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + value = 2; + status = safe_strtou64_base(absl::string_view(line.input), &value, base); + EXPECT_EQ(line.status, status) << line.input << " " << base; + EXPECT_EQ(line.value, value) << line.input << " " << base; + } + } +} + +} // namespace diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_cat.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_cat.cc new file mode 100644 index 00000000000..99eb28908c1 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_cat.cc @@ -0,0 +1,208 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/str_cat.h" + +#include +#include +#include +#include + +#include "absl/strings/ascii.h" +#include "absl/strings/internal/resize_uninitialized.h" + +namespace absl { + +AlphaNum::AlphaNum(Hex hex) { + char* const end = &digits_[numbers_internal::kFastToBufferSize]; + char* writer = end; + uint64_t value = hex.value; + static const char hexdigits[] = "0123456789abcdef"; + do { + *--writer = hexdigits[value & 0xF]; + value >>= 4; + } while (value != 0); + + char* beg; + if (end - writer < hex.width) { + beg = end - hex.width; + std::fill_n(beg, writer - beg, hex.fill); + } else { + beg = writer; + } + + piece_ = absl::string_view(beg, end - beg); +} + +// ---------------------------------------------------------------------- +// StrCat() +// This merges the given strings or integers, with no delimiter. This +// is designed to be the fastest possible way to construct a std::string out +// of a mix of raw C strings, StringPieces, strings, and integer values. +// ---------------------------------------------------------------------- + +// Append is merely a version of memcpy that returns the address of the byte +// after the area just overwritten. +static char* Append(char* out, const AlphaNum& x) { + // memcpy is allowed to overwrite arbitrary memory, so doing this after the + // call would force an extra fetch of x.size(). + char* after = out + x.size(); + memcpy(out, x.data(), x.size()); + return after; +} + +std::string StrCat(const AlphaNum& a, const AlphaNum& b) { + std::string result; + absl::strings_internal::STLStringResizeUninitialized(&result, + a.size() + b.size()); + char* const begin = &*result.begin(); + char* out = begin; + out = Append(out, a); + out = Append(out, b); + assert(out == begin + result.size()); + return result; +} + +std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c) { + std::string result; + strings_internal::STLStringResizeUninitialized( + &result, a.size() + b.size() + c.size()); + char* const begin = &*result.begin(); + char* out = begin; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + assert(out == begin + result.size()); + return result; +} + +std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c, + const AlphaNum& d) { + std::string result; + strings_internal::STLStringResizeUninitialized( + &result, a.size() + b.size() + c.size() + d.size()); + char* const begin = &*result.begin(); + char* out = begin; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + out = Append(out, d); + assert(out == begin + result.size()); + return result; +} + +namespace strings_internal { + +// Do not call directly - these are not part of the public API. +std::string CatPieces(std::initializer_list pieces) { + std::string result; + size_t total_size = 0; + for (const absl::string_view piece : pieces) total_size += piece.size(); + strings_internal::STLStringResizeUninitialized(&result, total_size); + + char* const begin = &*result.begin(); + char* out = begin; + for (const absl::string_view piece : pieces) { + const size_t this_size = piece.size(); + memcpy(out, piece.data(), this_size); + out += this_size; + } + assert(out == begin + result.size()); + return result; +} + +// It's possible to call StrAppend with an absl::string_view that is itself a +// fragment of the std::string we're appending to. However the results of this are +// random. Therefore, check for this in debug mode. Use unsigned math so we +// only have to do one comparison. Note, there's an exception case: appending an +// empty std::string is always allowed. +#define ASSERT_NO_OVERLAP(dest, src) \ + assert(((src).size() == 0) || \ + (uintptr_t((src).data() - (dest).data()) > uintptr_t((dest).size()))) + +void AppendPieces(std::string* dest, + std::initializer_list pieces) { + size_t old_size = dest->size(); + size_t total_size = old_size; + for (const absl::string_view piece : pieces) { + ASSERT_NO_OVERLAP(*dest, piece); + total_size += piece.size(); + } + strings_internal::STLStringResizeUninitialized(dest, total_size); + + char* const begin = &*dest->begin(); + char* out = begin + old_size; + for (const absl::string_view piece : pieces) { + const size_t this_size = piece.size(); + memcpy(out, piece.data(), this_size); + out += this_size; + } + assert(out == begin + dest->size()); +} + +} // namespace strings_internal + +void StrAppend(std::string* dest, const AlphaNum& a) { + ASSERT_NO_OVERLAP(*dest, a); + dest->append(a.data(), a.size()); +} + +void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b) { + ASSERT_NO_OVERLAP(*dest, a); + ASSERT_NO_OVERLAP(*dest, b); + std::string::size_type old_size = dest->size(); + strings_internal::STLStringResizeUninitialized( + dest, old_size + a.size() + b.size()); + char* const begin = &*dest->begin(); + char* out = begin + old_size; + out = Append(out, a); + out = Append(out, b); + assert(out == begin + dest->size()); +} + +void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c) { + ASSERT_NO_OVERLAP(*dest, a); + ASSERT_NO_OVERLAP(*dest, b); + ASSERT_NO_OVERLAP(*dest, c); + std::string::size_type old_size = dest->size(); + strings_internal::STLStringResizeUninitialized( + dest, old_size + a.size() + b.size() + c.size()); + char* const begin = &*dest->begin(); + char* out = begin + old_size; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + assert(out == begin + dest->size()); +} + +void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c, const AlphaNum& d) { + ASSERT_NO_OVERLAP(*dest, a); + ASSERT_NO_OVERLAP(*dest, b); + ASSERT_NO_OVERLAP(*dest, c); + ASSERT_NO_OVERLAP(*dest, d); + std::string::size_type old_size = dest->size(); + strings_internal::STLStringResizeUninitialized( + dest, old_size + a.size() + b.size() + c.size() + d.size()); + char* const begin = &*dest->begin(); + char* out = begin + old_size; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + out = Append(out, d); + assert(out == begin + dest->size()); +} + +} // namespace absl diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_cat.h b/Firestore/third_party/abseil-cpp/absl/strings/str_cat.h new file mode 100644 index 00000000000..1cf7b11fec7 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_cat.h @@ -0,0 +1,347 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: str_cat.h +// ----------------------------------------------------------------------------- +// +// This package contains functions for efficiently concatenating and appending +// strings: `StrCat()` and `StrAppend()`. Most of the work within these routines +// is actually handled through use of a special AlphaNum type, which was +// designed to be used as a parameter type that efficiently manages conversion +// to strings and avoids copies in the above operations. +// +// Any routine accepting either a std::string or a number may accept `AlphaNum`. +// The basic idea is that by accepting a `const AlphaNum &` as an argument +// to your function, your callers will automagically convert bools, integers, +// and floating point values to strings for you. +// +// NOTE: Use of `AlphaNum` outside of the //absl/strings package is unsupported +// except for the specific case of function parameters of type `AlphaNum` or +// `const AlphaNum &`. In particular, instantiating `AlphaNum` directly as a +// stack variable is not supported. +// +// Conversion from 8-bit values is not accepted because, if it were, then an +// attempt to pass ':' instead of ":" might result in a 58 ending up in your +// result. +// +// Bools convert to "0" or "1". +// +// Floating point numbers are formatted with six-digit precision, which is +// the default for "std::cout <<" or printf "%g" (the same as "%.6g"). +// +// +// You can convert to hexadecimal output rather than decimal output using the +// `Hex` type contained here. To do so, pass `Hex(my_int)` as a parameter to +// `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using +// a `PadSpec` enum. +// +// ----------------------------------------------------------------------------- + +#ifndef ABSL_STRINGS_STR_CAT_H_ +#define ABSL_STRINGS_STR_CAT_H_ + +#include +#include +#include +#include + +#include "absl/base/port.h" +#include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" + +namespace absl { + +namespace strings_internal { +// AlphaNumBuffer allows a way to pass a std::string to StrCat without having to do +// memory allocation. It is simply a pair of a fixed-size character array, and +// a size. Please don't use outside of absl, yet. +template +struct AlphaNumBuffer { + std::array data; + size_t size; +}; + +} // namespace strings_internal + +// Enum that specifies the number of significant digits to return in a `Hex` +// conversion and fill character to use. A `kZeroPad2` value, for example, would +// produce hexadecimal strings such as "0A","0F" and 'kSpacePad5' value would +// produce hexadecimal strings such as " A"," F". +enum PadSpec { + kNoPad = 1, + kZeroPad2, + kZeroPad3, + kZeroPad4, + kZeroPad5, + kZeroPad6, + kZeroPad7, + kZeroPad8, + kZeroPad9, + kZeroPad10, + kZeroPad11, + kZeroPad12, + kZeroPad13, + kZeroPad14, + kZeroPad15, + kZeroPad16, + + kSpacePad2 = kZeroPad2 + 64, + kSpacePad3, + kSpacePad4, + kSpacePad5, + kSpacePad6, + kSpacePad7, + kSpacePad8, + kSpacePad9, + kSpacePad10, + kSpacePad11, + kSpacePad12, + kSpacePad13, + kSpacePad14, + kSpacePad15, + kSpacePad16, +}; + +// ----------------------------------------------------------------------------- +// Hex +// ----------------------------------------------------------------------------- +// +// `Hex` stores a set of hexadecimal std::string conversion parameters for use +// within `AlphaNum` std::string conversions. +struct Hex { + uint64_t value; + uint8_t width; + char fill; + + template + explicit Hex(Int v, PadSpec spec = absl::kNoPad, + typename std::enable_if::type* = nullptr) + : Hex(spec, static_cast(v)) {} + template + explicit Hex(Int v, PadSpec spec = absl::kNoPad, + typename std::enable_if::type* = nullptr) + : Hex(spec, static_cast(v)) {} + template + explicit Hex(Int v, PadSpec spec = absl::kNoPad, + typename std::enable_if::type* = nullptr) + : Hex(spec, static_cast(v)) {} + template + explicit Hex(Int v, PadSpec spec = absl::kNoPad, + typename std::enable_if::type* = nullptr) + : Hex(spec, static_cast(v)) {} + + private: + Hex(PadSpec spec, uint64_t v) + : value(v), + width(spec == absl::kNoPad + ? 1 + : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2 + : spec - absl::kZeroPad2 + 2), + fill(spec >= absl::kSpacePad2 ? ' ' : '0') {} +}; + +// ----------------------------------------------------------------------------- +// AlphaNum +// ----------------------------------------------------------------------------- +// +// The `AlphaNum` class acts as the main parameter type for `StrCat()` and +// `StrAppend()`, providing efficient conversion of numeric, boolean, and +// hexadecimal values (through the `Hex` type) into strings. + +class AlphaNum { + public: + // No bool ctor -- bools convert to an integral type. + // A bool ctor would also convert incoming pointers (bletch). + + AlphaNum(int x) // NOLINT(runtime/explicit) + : piece_(digits_, + numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {} + AlphaNum(unsigned int x) // NOLINT(runtime/explicit) + : piece_(digits_, + numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {} + AlphaNum(long x) // NOLINT(*) + : piece_(digits_, + numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {} + AlphaNum(unsigned long x) // NOLINT(*) + : piece_(digits_, + numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {} + AlphaNum(long long x) // NOLINT(*) + : piece_(digits_, + numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {} + AlphaNum(unsigned long long x) // NOLINT(*) + : piece_(digits_, + numbers_internal::FastIntToBuffer(x, digits_) - &digits_[0]) {} + + AlphaNum(float f) // NOLINT(runtime/explicit) + : piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {} + AlphaNum(double f) // NOLINT(runtime/explicit) + : piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {} + + AlphaNum(Hex hex); // NOLINT(runtime/explicit) + + template + AlphaNum( // NOLINT(runtime/explicit) + const strings_internal::AlphaNumBuffer& buf) + : piece_(&buf.data[0], buf.size) {} + + AlphaNum(const char* c_str) : piece_(c_str) {} // NOLINT(runtime/explicit) + AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit) + template + AlphaNum( // NOLINT(runtime/explicit) + const std::basic_string, Allocator>& str) + : piece_(str) {} + + // Use std::string literals ":" instead of character literals ':'. + AlphaNum(char c) = delete; // NOLINT(runtime/explicit) + + AlphaNum(const AlphaNum&) = delete; + AlphaNum& operator=(const AlphaNum&) = delete; + + absl::string_view::size_type size() const { return piece_.size(); } + const char* data() const { return piece_.data(); } + absl::string_view Piece() const { return piece_; } + + // Normal enums are already handled by the integer formatters. + // This overload matches only scoped enums. + template {} && !std::is_convertible{}>::type> + AlphaNum(T e) // NOLINT(runtime/explicit) + : AlphaNum(static_cast::type>(e)) {} + + private: + absl::string_view piece_; + char digits_[numbers_internal::kFastToBufferSize]; +}; + +// ----------------------------------------------------------------------------- +// StrCat() +// ----------------------------------------------------------------------------- +// +// Merges given strings or numbers, using no delimiter(s). +// +// `StrCat()` is designed to be the fastest possible way to construct a std::string +// out of a mix of raw C strings, string_views, strings, bool values, +// and numeric values. +// +// Don't use `StrCat()` for user-visible strings. The localization process +// works poorly on strings built up out of fragments. +// +// For clarity and performance, don't use `StrCat()` when appending to a +// std::string. Use `StrAppend()` instead. In particular, avoid using any of these +// (anti-)patterns: +// +// str.append(StrCat(...)) +// str += StrCat(...) +// str = StrCat(str, ...) +// +// The last case is the worst, with a potential to change a loop +// from a linear time operation with O(1) dynamic allocations into a +// quadratic time operation with O(n) dynamic allocations. +// +// See `StrAppend()` below for more information. + +namespace strings_internal { + +// Do not call directly - this is not part of the public API. +std::string CatPieces(std::initializer_list pieces); +void AppendPieces(std::string* dest, + std::initializer_list pieces); + +} // namespace strings_internal + +ABSL_MUST_USE_RESULT inline std::string StrCat() { return std::string(); } + +ABSL_MUST_USE_RESULT inline std::string StrCat(const AlphaNum& a) { + return std::string(a.data(), a.size()); +} + +ABSL_MUST_USE_RESULT std::string StrCat(const AlphaNum& a, const AlphaNum& b); +ABSL_MUST_USE_RESULT std::string StrCat(const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c); +ABSL_MUST_USE_RESULT std::string StrCat(const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c, const AlphaNum& d); + +// Support 5 or more arguments +template +ABSL_MUST_USE_RESULT inline std::string StrCat(const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c, const AlphaNum& d, + const AlphaNum& e, + const AV&... args) { + return strings_internal::CatPieces( + {a.Piece(), b.Piece(), c.Piece(), d.Piece(), e.Piece(), + static_cast(args).Piece()...}); +} + +// ----------------------------------------------------------------------------- +// StrAppend() +// ----------------------------------------------------------------------------- +// +// Appends a std::string or set of strings to an existing std::string, in a similar +// fashion to `StrCat()`. +// +// WARNING: `StrAppend(&str, a, b, c, ...)` requires that none of the +// a, b, c, parameters be a reference into str. For speed, `StrAppend()` does +// not try to check each of its input arguments to be sure that they are not +// a subset of the std::string being appended to. That is, while this will work: +// +// std::string s = "foo"; +// s += s; +// +// This output is undefined: +// +// std::string s = "foo"; +// StrAppend(&s, s); +// +// This output is undefined as well, since `absl::string_view` does not own its +// data: +// +// std::string s = "foobar"; +// absl::string_view p = s; +// StrAppend(&s, p); + +inline void StrAppend(std::string*) {} +void StrAppend(std::string* dest, const AlphaNum& a); +void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b); +void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c); +void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c, const AlphaNum& d); + +// Support 5 or more arguments +template +inline void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, + const AlphaNum& c, const AlphaNum& d, const AlphaNum& e, + const AV&... args) { + strings_internal::AppendPieces( + dest, {a.Piece(), b.Piece(), c.Piece(), d.Piece(), e.Piece(), + static_cast(args).Piece()...}); +} + +// Helper function for the future StrCat default floating-point format, %.6g +// This is fast. +inline strings_internal::AlphaNumBuffer< + numbers_internal::kSixDigitsToBufferSize> +SixDigits(double d) { + strings_internal::AlphaNumBuffer + result; + result.size = numbers_internal::SixDigitsToBuffer(d, &result.data[0]); + return result; +} + +} // namespace absl + +#endif // ABSL_STRINGS_STR_CAT_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_cat_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_cat_test.cc new file mode 100644 index 00000000000..dd063b015b6 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/str_cat_test.cc @@ -0,0 +1,468 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Unit tests for all str_cat.h functions + +#include "absl/strings/str_cat.h" + +#include +#include + +#include "gtest/gtest.h" +#include "absl/strings/substitute.h" + +namespace { + +// Test absl::StrCat of ints and longs of various sizes and signdedness. +TEST(StrCat, Ints) { + const short s = -1; // NOLINT(runtime/int) + const uint16_t us = 2; + const int i = -3; + const unsigned int ui = 4; + const long l = -5; // NOLINT(runtime/int) + const unsigned long ul = 6; // NOLINT(runtime/int) + const long long ll = -7; // NOLINT(runtime/int) + const unsigned long long ull = 8; // NOLINT(runtime/int) + const ptrdiff_t ptrdiff = -9; + const size_t size = 10; + const intptr_t intptr = -12; + const uintptr_t uintptr = 13; + std::string answer; + answer = absl::StrCat(s, us); + EXPECT_EQ(answer, "-12"); + answer = absl::StrCat(i, ui); + EXPECT_EQ(answer, "-34"); + answer = absl::StrCat(l, ul); + EXPECT_EQ(answer, "-56"); + answer = absl::StrCat(ll, ull); + EXPECT_EQ(answer, "-78"); + answer = absl::StrCat(ptrdiff, size); + EXPECT_EQ(answer, "-910"); + answer = absl::StrCat(ptrdiff, intptr); + EXPECT_EQ(answer, "-9-12"); + answer = absl::StrCat(uintptr, 0); + EXPECT_EQ(answer, "130"); +} + +TEST(StrCat, Enums) { + enum SmallNumbers { One = 1, Ten = 10 } e = Ten; + EXPECT_EQ("10", absl::StrCat(e)); + EXPECT_EQ("-5", absl::StrCat(SmallNumbers(-5))); + + enum class Option { Boxers = 1, Briefs = -1 }; + + EXPECT_EQ("-1", absl::StrCat(Option::Briefs)); + + enum class Airplane : uint64_t { + Airbus = 1, + Boeing = 1000, + Canary = 10000000000 // too big for "int" + }; + + EXPECT_EQ("10000000000", absl::StrCat(Airplane::Canary)); + + enum class TwoGig : int32_t { + TwoToTheZero = 1, + TwoToTheSixteenth = 1 << 16, + TwoToTheThirtyFirst = INT32_MIN + }; + EXPECT_EQ("65536", absl::StrCat(TwoGig::TwoToTheSixteenth)); + EXPECT_EQ("-2147483648", absl::StrCat(TwoGig::TwoToTheThirtyFirst)); + EXPECT_EQ("-1", absl::StrCat(static_cast(-1))); + + enum class FourGig : uint32_t { + TwoToTheZero = 1, + TwoToTheSixteenth = 1 << 16, + TwoToTheThirtyFirst = 1U << 31 // too big for "int" + }; + EXPECT_EQ("65536", absl::StrCat(FourGig::TwoToTheSixteenth)); + EXPECT_EQ("2147483648", absl::StrCat(FourGig::TwoToTheThirtyFirst)); + EXPECT_EQ("4294967295", absl::StrCat(static_cast(-1))); + + EXPECT_EQ("10000000000", absl::StrCat(Airplane::Canary)); +} + +TEST(StrCat, Basics) { + std::string result; + + std::string strs[] = { + "Hello", + "Cruel", + "World" + }; + + std::string stdstrs[] = { + "std::Hello", + "std::Cruel", + "std::World" + }; + + absl::string_view pieces[] = {"Hello", "Cruel", "World"}; + + const char* c_strs[] = { + "Hello", + "Cruel", + "World" + }; + + int32_t i32s[] = {'H', 'C', 'W'}; + uint64_t ui64s[] = {12345678910LL, 10987654321LL}; + + EXPECT_EQ(absl::StrCat(), ""); + + result = absl::StrCat(false, true, 2, 3); + EXPECT_EQ(result, "0123"); + + result = absl::StrCat(-1); + EXPECT_EQ(result, "-1"); + + result = absl::StrCat(absl::SixDigits(0.5)); + EXPECT_EQ(result, "0.5"); + + result = absl::StrCat(strs[1], pieces[2]); + EXPECT_EQ(result, "CruelWorld"); + + result = absl::StrCat(stdstrs[1], " ", stdstrs[2]); + EXPECT_EQ(result, "std::Cruel std::World"); + + result = absl::StrCat(strs[0], ", ", pieces[2]); + EXPECT_EQ(result, "Hello, World"); + + result = absl::StrCat(strs[0], ", ", strs[1], " ", strs[2], "!"); + EXPECT_EQ(result, "Hello, Cruel World!"); + + result = absl::StrCat(pieces[0], ", ", pieces[1], " ", pieces[2]); + EXPECT_EQ(result, "Hello, Cruel World"); + + result = absl::StrCat(c_strs[0], ", ", c_strs[1], " ", c_strs[2]); + EXPECT_EQ(result, "Hello, Cruel World"); + + result = absl::StrCat("ASCII ", i32s[0], ", ", i32s[1], " ", i32s[2], "!"); + EXPECT_EQ(result, "ASCII 72, 67 87!"); + + result = absl::StrCat(ui64s[0], ", ", ui64s[1], "!"); + EXPECT_EQ(result, "12345678910, 10987654321!"); + + std::string one = "1"; // Actually, it's the size of this std::string that we want; a + // 64-bit build distinguishes between size_t and uint64_t, + // even though they're both unsigned 64-bit values. + result = absl::StrCat("And a ", one.size(), " and a ", + &result[2] - &result[0], " and a ", one, " 2 3 4", "!"); + EXPECT_EQ(result, "And a 1 and a 2 and a 1 2 3 4!"); + + // result = absl::StrCat("Single chars won't compile", '!'); + // result = absl::StrCat("Neither will nullptrs", nullptr); + result = + absl::StrCat("To output a char by ASCII/numeric value, use +: ", '!' + 0); + EXPECT_EQ(result, "To output a char by ASCII/numeric value, use +: 33"); + + float f = 100000.5; + result = absl::StrCat("A hundred K and a half is ", absl::SixDigits(f)); + EXPECT_EQ(result, "A hundred K and a half is 100000"); + + f = 100001.5; + result = + absl::StrCat("A hundred K and one and a half is ", absl::SixDigits(f)); + EXPECT_EQ(result, "A hundred K and one and a half is 100002"); + + double d = 100000.5; + d *= d; + result = + absl::StrCat("A hundred K and a half squared is ", absl::SixDigits(d)); + EXPECT_EQ(result, "A hundred K and a half squared is 1.00001e+10"); + + result = absl::StrCat(1, 2, 333, 4444, 55555, 666666, 7777777, 88888888, + 999999999); + EXPECT_EQ(result, "12333444455555666666777777788888888999999999"); +} + +// A minimal allocator that uses malloc(). +template +struct Mallocator { + typedef T value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + + size_type max_size() const { + return size_t(std::numeric_limits::max()) / sizeof(value_type); + } + template + struct rebind { + typedef Mallocator other; + }; + Mallocator() = default; + + T* allocate(size_t n) { return static_cast(std::malloc(n * sizeof(T))); } + void deallocate(T* p, size_t) { std::free(p); } +}; +template +bool operator==(const Mallocator&, const Mallocator&) { + return true; +} +template +bool operator!=(const Mallocator&, const Mallocator&) { + return false; +} + +TEST(StrCat, CustomAllocator) { + using mstring = + std::basic_string, Mallocator>; + const mstring str1("PARACHUTE OFF A BLIMP INTO MOSCONE!!"); + + const mstring str2("Read this book about coffee tables"); + + std::string result = absl::StrCat(str1, str2); + EXPECT_EQ(result, + "PARACHUTE OFF A BLIMP INTO MOSCONE!!" + "Read this book about coffee tables"); +} + +TEST(StrCat, MaxArgs) { + std::string result; + // Test 10 up to 26 arguments, the current maximum + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a"); + EXPECT_EQ(result, "123456789a"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b"); + EXPECT_EQ(result, "123456789ab"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c"); + EXPECT_EQ(result, "123456789abc"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d"); + EXPECT_EQ(result, "123456789abcd"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e"); + EXPECT_EQ(result, "123456789abcde"); + result = + absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f"); + EXPECT_EQ(result, "123456789abcdef"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g"); + EXPECT_EQ(result, "123456789abcdefg"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h"); + EXPECT_EQ(result, "123456789abcdefgh"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i"); + EXPECT_EQ(result, "123456789abcdefghi"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j"); + EXPECT_EQ(result, "123456789abcdefghij"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k"); + EXPECT_EQ(result, "123456789abcdefghijk"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l"); + EXPECT_EQ(result, "123456789abcdefghijkl"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m"); + EXPECT_EQ(result, "123456789abcdefghijklm"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n"); + EXPECT_EQ(result, "123456789abcdefghijklmn"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o"); + EXPECT_EQ(result, "123456789abcdefghijklmno"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o", "p"); + EXPECT_EQ(result, "123456789abcdefghijklmnop"); + result = absl::StrCat(1, 2, 3, 4, 5, 6, 7, 8, 9, "a", "b", "c", "d", "e", "f", + "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q"); + EXPECT_EQ(result, "123456789abcdefghijklmnopq"); + // No limit thanks to C++11's variadic templates + result = absl::StrCat( + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "a", "b", "c", "d", "e", "f", "g", "h", + "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", + "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"); + EXPECT_EQ(result, + "12345678910abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); +} + +TEST(StrAppend, Basics) { + std::string result = "existing text"; + + std::string strs[] = { + "Hello", + "Cruel", + "World" + }; + + std::string stdstrs[] = { + "std::Hello", + "std::Cruel", + "std::World" + }; + + absl::string_view pieces[] = {"Hello", "Cruel", "World"}; + + const char* c_strs[] = { + "Hello", + "Cruel", + "World" + }; + + int32_t i32s[] = {'H', 'C', 'W'}; + uint64_t ui64s[] = {12345678910LL, 10987654321LL}; + + std::string::size_type old_size = result.size(); + absl::StrAppend(&result); + EXPECT_EQ(result.size(), old_size); + + old_size = result.size(); + absl::StrAppend(&result, strs[0]); + EXPECT_EQ(result.substr(old_size), "Hello"); + + old_size = result.size(); + absl::StrAppend(&result, strs[1], pieces[2]); + EXPECT_EQ(result.substr(old_size), "CruelWorld"); + + old_size = result.size(); + absl::StrAppend(&result, stdstrs[0], ", ", pieces[2]); + EXPECT_EQ(result.substr(old_size), "std::Hello, World"); + + old_size = result.size(); + absl::StrAppend(&result, strs[0], ", ", stdstrs[1], " ", strs[2], "!"); + EXPECT_EQ(result.substr(old_size), "Hello, std::Cruel World!"); + + old_size = result.size(); + absl::StrAppend(&result, pieces[0], ", ", pieces[1], " ", pieces[2]); + EXPECT_EQ(result.substr(old_size), "Hello, Cruel World"); + + old_size = result.size(); + absl::StrAppend(&result, c_strs[0], ", ", c_strs[1], " ", c_strs[2]); + EXPECT_EQ(result.substr(old_size), "Hello, Cruel World"); + + old_size = result.size(); + absl::StrAppend(&result, "ASCII ", i32s[0], ", ", i32s[1], " ", i32s[2], "!"); + EXPECT_EQ(result.substr(old_size), "ASCII 72, 67 87!"); + + old_size = result.size(); + absl::StrAppend(&result, ui64s[0], ", ", ui64s[1], "!"); + EXPECT_EQ(result.substr(old_size), "12345678910, 10987654321!"); + + std::string one = "1"; // Actually, it's the size of this std::string that we want; a + // 64-bit build distinguishes between size_t and uint64_t, + // even though they're both unsigned 64-bit values. + old_size = result.size(); + absl::StrAppend(&result, "And a ", one.size(), " and a ", + &result[2] - &result[0], " and a ", one, " 2 3 4", "!"); + EXPECT_EQ(result.substr(old_size), "And a 1 and a 2 and a 1 2 3 4!"); + + // result = absl::StrCat("Single chars won't compile", '!'); + // result = absl::StrCat("Neither will nullptrs", nullptr); + old_size = result.size(); + absl::StrAppend(&result, + "To output a char by ASCII/numeric value, use +: ", '!' + 0); + EXPECT_EQ(result.substr(old_size), + "To output a char by ASCII/numeric value, use +: 33"); + + // Test 9 arguments, the old maximum + old_size = result.size(); + absl::StrAppend(&result, 1, 22, 333, 4444, 55555, 666666, 7777777, 88888888, + 9); + EXPECT_EQ(result.substr(old_size), "1223334444555556666667777777888888889"); + + // No limit thanks to C++11's variadic templates + old_size = result.size(); + absl::StrAppend( + &result, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", // + "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", // + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", // + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", // + "No limit thanks to C++11's variadic templates"); + EXPECT_EQ(result.substr(old_size), + "12345678910abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + "No limit thanks to C++11's variadic templates"); +} + +#ifdef GTEST_HAS_DEATH_TEST +TEST(StrAppend, Death) { + std::string s = "self"; + // on linux it's "assertion", on mac it's "Assertion", + // on chromiumos it's "Assertion ... failed". + EXPECT_DEBUG_DEATH(absl::StrAppend(&s, s.c_str() + 1), "ssertion.*failed"); + EXPECT_DEBUG_DEATH(absl::StrAppend(&s, s), "ssertion.*failed"); +} +#endif // GTEST_HAS_DEATH_TEST + +TEST(StrAppend, EmptyString) { + std::string s = ""; + absl::StrAppend(&s, s); + EXPECT_EQ(s, ""); +} + +template +void CheckHex(IntType v, const char* nopad_format, const char* zeropad_format, + const char* spacepad_format) { + char expected[256]; + + std::string actual = absl::StrCat(absl::Hex(v, absl::kNoPad)); + snprintf(expected, sizeof(expected), nopad_format, v); + EXPECT_EQ(expected, actual) << " decimal value " << v; + + for (int spec = absl::kZeroPad2; spec <= absl::kZeroPad16; ++spec) { + std::string actual = + absl::StrCat(absl::Hex(v, static_cast(spec))); + snprintf(expected, sizeof(expected), zeropad_format, + spec - absl::kZeroPad2 + 2, v); + EXPECT_EQ(expected, actual) << " decimal value " << v; + } + + for (int spec = absl::kSpacePad2; spec <= absl::kSpacePad16; ++spec) { + std::string actual = + absl::StrCat(absl::Hex(v, static_cast(spec))); + snprintf(expected, sizeof(expected), spacepad_format, + spec - absl::kSpacePad2 + 2, v); + EXPECT_EQ(expected, actual) << " decimal value " << v; + } +} + +void CheckHex64(uint64_t v) { + unsigned long long llv = v; // NOLINT(runtime/int) + + CheckHex(llv, "%llx", "%0*llx", "%*llx"); +} + +template +void CheckHex32(Int32Type v) { + CheckHex(v, "%x", "%0*x", "%*x"); +} + +void TestFastPrints() { + // Test min int to make sure that works + for (int i = 0; i < 10000; i++) { + CheckHex64(i); + CheckHex32(static_cast(i)); + CheckHex32(i); + CheckHex32(-i); + } + + CheckHex64(uint64_t{0x123456789abcdef0}); + CheckHex32(0x12345678U); + + int8_t minus_one_8bit = -1; + EXPECT_EQ("ff", absl::StrCat(absl::Hex(minus_one_8bit))); + + int16_t minus_one_16bit = -1; + EXPECT_EQ("ffff", absl::StrCat(absl::Hex(minus_one_16bit))); +} + +TEST(Numbers, TestFunctionsMovedOverFromNumbersMain) { + TestFastPrints(); +} + +} // namespace diff --git a/Firestore/third_party/abseil-cpp/absl/strings/strip.cc b/Firestore/third_party/abseil-cpp/absl/strings/strip.cc new file mode 100644 index 00000000000..929c05d7236 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/strip.cc @@ -0,0 +1 @@ +404: Not Found diff --git a/Firestore/third_party/abseil-cpp/absl/strings/strip.h b/Firestore/third_party/abseil-cpp/absl/strings/strip.h new file mode 100644 index 00000000000..2f8d21f7deb --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/strip.h @@ -0,0 +1,89 @@ +// +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: strip.h +// ----------------------------------------------------------------------------- +// +// This file contains various functions for stripping substrings from a std::string. +#ifndef ABSL_STRINGS_STRIP_H_ +#define ABSL_STRINGS_STRIP_H_ + +#include +#include + +#include "absl/base/macros.h" +#include "absl/strings/ascii.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" + +namespace absl { + +// ConsumePrefix() +// +// Strips the `expected` prefix from the start of the given std::string, returning +// `true` if the strip operation succeeded or false otherwise. +// +// Example: +// +// absl::string_view input("abc"); +// EXPECT_TRUE(absl::ConsumePrefix(&input, "a")); +// EXPECT_EQ(input, "bc"); +inline bool ConsumePrefix(absl::string_view* str, absl::string_view expected) { + if (!absl::StartsWith(*str, expected)) return false; + str->remove_prefix(expected.size()); + return true; +} +// ConsumeSuffix() +// +// Strips the `expected` suffix from the end of the given std::string, returning +// `true` if the strip operation succeeded or false otherwise. +// +// Example: +// +// absl::string_view input("abcdef"); +// EXPECT_TRUE(absl::ConsumeSuffix(&input, "def")); +// EXPECT_EQ(input, "abc"); +inline bool ConsumeSuffix(absl::string_view* str, absl::string_view expected) { + if (!absl::EndsWith(*str, expected)) return false; + str->remove_suffix(expected.size()); + return true; +} + +// StripPrefix() +// +// Returns a view into the input std::string 'str' with the given 'prefix' removed, +// but leaving the original std::string intact. If the prefix does not match at the +// start of the std::string, returns the original std::string instead. +ABSL_MUST_USE_RESULT inline absl::string_view StripPrefix( + absl::string_view str, absl::string_view prefix) { + if (absl::StartsWith(str, prefix)) str.remove_prefix(prefix.size()); + return str; +} + +// StripSuffix() +// +// Returns a view into the input std::string 'str' with the given 'suffix' removed, +// but leaving the original std::string intact. If the suffix does not match at the +// end of the std::string, returns the original std::string instead. +ABSL_MUST_USE_RESULT inline absl::string_view StripSuffix( + absl::string_view str, absl::string_view suffix) { + if (absl::EndsWith(str, suffix)) str.remove_suffix(suffix.size()); + return str; +} + +} // namespace absl + +#endif // ABSL_STRINGS_STRIP_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/strip_test.cc b/Firestore/third_party/abseil-cpp/absl/strings/strip_test.cc new file mode 100644 index 00000000000..205c160c192 --- /dev/null +++ b/Firestore/third_party/abseil-cpp/absl/strings/strip_test.cc @@ -0,0 +1,201 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains functions that remove a defined part from the std::string, +// i.e., strip the std::string. + +#include "absl/strings/strip.h" + +#include +#include +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/strings/string_view.h" + +namespace { + +using testing::ElementsAre; +using testing::IsEmpty; + +TEST(Strip, ConsumePrefixOneChar) { + absl::string_view input("abc"); + EXPECT_TRUE(absl::ConsumePrefix(&input, "a")); + EXPECT_EQ(input, "bc"); + + EXPECT_FALSE(absl::ConsumePrefix(&input, "x")); + EXPECT_EQ(input, "bc"); + + EXPECT_TRUE(absl::ConsumePrefix(&input, "b")); + EXPECT_EQ(input, "c"); + + EXPECT_TRUE(absl::ConsumePrefix(&input, "c")); + EXPECT_EQ(input, ""); + + EXPECT_FALSE(absl::ConsumePrefix(&input, "a")); + EXPECT_EQ(input, ""); +} + +TEST(Strip, ConsumePrefix) { + absl::string_view input("abcdef"); + EXPECT_FALSE(absl::ConsumePrefix(&input, "abcdefg")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_FALSE(absl::ConsumePrefix(&input, "abce")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_TRUE(absl::ConsumePrefix(&input, "")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_FALSE(absl::ConsumePrefix(&input, "abcdeg")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_TRUE(absl::ConsumePrefix(&input, "abcdef")); + EXPECT_EQ(input, ""); + + input = "abcdef"; + EXPECT_TRUE(absl::ConsumePrefix(&input, "abcde")); + EXPECT_EQ(input, "f"); +} + +TEST(Strip, ConsumeSuffix) { + absl::string_view input("abcdef"); + EXPECT_FALSE(absl::ConsumeSuffix(&input, "abcdefg")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_TRUE(absl::ConsumeSuffix(&input, "")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_TRUE(absl::ConsumeSuffix(&input, "def")); + EXPECT_EQ(input, "abc"); + + input = "abcdef"; + EXPECT_FALSE(absl::ConsumeSuffix(&input, "abcdeg")); + EXPECT_EQ(input, "abcdef"); + + EXPECT_TRUE(absl::ConsumeSuffix(&input, "f")); + EXPECT_EQ(input, "abcde"); + + EXPECT_TRUE(absl::ConsumeSuffix(&input, "abcde")); + EXPECT_EQ(input, ""); +} + +TEST(Strip, StripPrefix) { + const absl::string_view null_str; + + EXPECT_EQ(absl::StripPrefix("foobar", "foo"), "bar"); + EXPECT_EQ(absl::StripPrefix("foobar", ""), "foobar"); + EXPECT_EQ(absl::StripPrefix("foobar", null_str), "foobar"); + EXPECT_EQ(absl::StripPrefix("foobar", "foobar"), ""); + EXPECT_EQ(absl::StripPrefix("foobar", "bar"), "foobar"); + EXPECT_EQ(absl::StripPrefix("foobar", "foobarr"), "foobar"); + EXPECT_EQ(absl::StripPrefix("", ""), ""); +} + +TEST(Strip, StripSuffix) { + const absl::string_view null_str; + + EXPECT_EQ(absl::StripSuffix("foobar", "bar"), "foo"); + EXPECT_EQ(absl::StripSuffix("foobar", ""), "foobar"); + EXPECT_EQ(absl::StripSuffix("foobar", null_str), "foobar"); + EXPECT_EQ(absl::StripSuffix("foobar", "foobar"), ""); + EXPECT_EQ(absl::StripSuffix("foobar", "foo"), "foobar"); + EXPECT_EQ(absl::StripSuffix("foobar", "ffoobar"), "foobar"); + EXPECT_EQ(absl::StripSuffix("", ""), ""); +} + +TEST(Strip, RemoveExtraAsciiWhitespace) { + const char* inputs[] = { + "No extra space", + " Leading whitespace", + "Trailing whitespace ", + " Leading and trailing ", + " Whitespace \t in\v middle ", + "'Eeeeep! \n Newlines!\n", + "nospaces", + }; + const char* outputs[] = { + "No extra space", + "Leading whitespace", + "Trailing whitespace", + "Leading and trailing", + "Whitespace in middle", + "'Eeeeep! Newlines!", + "nospaces", + }; + int NUM_TESTS = 7; + + for (int i = 0; i < NUM_TESTS; i++) { + std::string s(inputs[i]); + absl::RemoveExtraAsciiWhitespace(&s); + EXPECT_STREQ(outputs[i], s.c_str()); + } + + // Test that absl::RemoveExtraAsciiWhitespace returns immediately for empty + // strings (It was adding the \0 character to the C++ std::string, which broke + // tests involving empty()) + std::string zero_string = ""; + assert(zero_string.empty()); + absl::RemoveExtraAsciiWhitespace(&zero_string); + EXPECT_EQ(zero_string.size(), 0); + EXPECT_TRUE(zero_string.empty()); +} + +TEST(Strip, StripTrailingAsciiWhitespace) { + std::string test = "foo "; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, "foo"); + + test = " "; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, ""); + + test = ""; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, ""); + + test = " abc\t"; + absl::StripTrailingAsciiWhitespace(&test); + EXPECT_EQ(test, " abc"); +} + +TEST(String, StripLeadingAsciiWhitespace) { + absl::string_view orig = "\t \n\f\r\n\vfoo"; + EXPECT_EQ("foo", absl::StripLeadingAsciiWhitespace(orig)); + orig = "\t \n\f\r\v\n\t \n\f\r\v\n"; + EXPECT_EQ(absl::string_view(), absl::StripLeadingAsciiWhitespace(orig)); +} + +TEST(Strip, StripAsciiWhitespace) { + std::string test2 = "\t \f\r\n\vfoo \t\f\r\v\n"; + absl::StripAsciiWhitespace(&test2); + EXPECT_EQ(test2, "foo"); + std::string test3 = "bar"; + absl::StripAsciiWhitespace(&test3); + EXPECT_EQ(test3, "bar"); + std::string test4 = "\t \f\r\n\vfoo"; + absl::StripAsciiWhitespace(&test4); + EXPECT_EQ(test4, "foo"); + std::string test5 = "foo \t\f\r\v\n"; + absl::StripAsciiWhitespace(&test5); + EXPECT_EQ(test5, "foo"); + absl::string_view test6("\t \f\r\n\vfoo \t\f\r\v\n"); + test6 = absl::StripAsciiWhitespace(test6); + EXPECT_EQ(test6, "foo"); + test6 = absl::StripAsciiWhitespace(test6); + EXPECT_EQ(test6, "foo"); // already stripped +} + +} // namespace From 286111e74b7f5d4129c6a82c8cbaa2c8e84eed32 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 14:17:23 -0500 Subject: [PATCH 09/48] compilation wip --- Firestore/core/src/firebase/firestore/model/base_path.h | 6 +++--- .../core/src/firebase/firestore/model/resource_path.cc | 6 +++--- .../core/src/firebase/firestore/model/resource_path.h | 8 +++++--- .../third_party/abseil-cpp/absl/strings/CMakeLists.txt | 8 ++++++++ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 3d8c695515e..28f7b0befd9 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -45,7 +45,7 @@ class BasePath { return at(index); } - const std::string& at(const size_t index) const { + const std::string& at(const size_t i) const { FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), "index %u out of range", index); return segments_[i]; @@ -99,7 +99,7 @@ class BasePath { T WithoutLastElement() const { FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !empty(), "Cannot call WithoutLastElement() on empty path); + !empty(), "Cannot call WithoutLastElement() on empty path"); return T{segments_.begin(), segments_.end() - 1}; } @@ -120,7 +120,7 @@ class BasePath { BasePath(std::initializer_list list) : segments_{list.begin(), list.end()} { } - FieldPath(SegmentsT&& segments) : segments_{std::move(segments)} { + BasePath(SegmentsT&& segments) : segments_{std::move(segments)} { } ~BasePath() = default; diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index 78a56dcd255..7fb0afa2e52 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/model/field_path.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" @@ -28,7 +28,7 @@ namespace firebase { namespace firestore { namespace model { -static ResourcePath ResourcePath::Parse(const std::string& path) { +ResourcePath ResourcePath::Parse(const std::string& path) { // NOTE: The client is ignorant of any path segments containing escape // sequences (e.g. __id123__) and just passes them through raw (they exist // for legacy reasons and should not be used frequently). @@ -48,7 +48,7 @@ std::string ResourcePath::CanonicalString() const { // sequences (e.g. __id123__) and just passes them through raw (they exist // for legacy reasons and should not be used frequently). - return absl::StrJoin(begin(), end(), '/'); + return absl::StrJoin(begin(), end(), "/"); } } // namespace model diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index 5a7e0821d7d..f8ea5d8504e 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ -#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_FIELD_PATH_H_ +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_RESOURCE_PATH_H_ #include #include @@ -39,10 +39,12 @@ class ResourcePath : public impl::BasePath { std::string CanonicalString() const; private: - ResourcePath(SegmentsT&& segments) : BasePath{segments} { + ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { } }; } // namespace model } // namespace firestore } // namespace firebase + +#endif diff --git a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt index 84cd5c8d9f0..d7afd3f86fc 100644 --- a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt +++ b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt @@ -18,6 +18,8 @@ list(APPEND STRINGS_PUBLIC_HEADERS "ascii.h" "match.h" + "strip.h" + "str_cat.h" "str_join.h" "str_replace.h" "str_split.h" @@ -27,7 +29,10 @@ list(APPEND STRINGS_PUBLIC_HEADERS list(APPEND STRINGS_INTERNAL_HEADERS "internal/memutil.h" + "internal/ostringstream.h" "internal/resize_uninitialized.h" + "internal/str_join_internal.h" + "internal/str_split_internal.h" ) @@ -36,7 +41,10 @@ list(APPEND STRINGS_INTERNAL_HEADERS list(APPEND STRINGS_SRC "ascii.cc" "internal/memutil.cc" + "internal/ostringstream.cc" "match.cc" + "strip.cc" + "str_cat.cc" "str_join.cc" "str_replace.cc" "str_split.cc" From 6979f6274cfbf381e487541b514f5109ef200a5d Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 14:36:54 -0500 Subject: [PATCH 10/48] compiles and constructor test runs! --- Firestore/core/src/firebase/firestore/model/field_path.cc | 7 +++---- Firestore/core/src/firebase/firestore/model/field_path.h | 3 +++ .../core/src/firebase/firestore/model/resource_path.cc | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 5fe674291c5..6c8d90c5ba4 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -53,7 +53,7 @@ std::string EscapedSegment(const std::string& segment) { auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); const bool needs_escaping = !IsValidIdentifier(escaped); if (needs_escaping) { - escaped.push_front('`'); + escaped.insert(escaped.begin(), '`'); escaped.push_back('`'); } return escaped; @@ -71,7 +71,7 @@ FieldPath FieldPath::ParseServerFormat(const std::string& path) { std::string segment; segment.reserve(path.size()); - const auto finish_segment = [&segments, &segment] { + const auto finish_segment = [&segments, &segment, &path] { FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( !segment.empty(), "Invalid field path (%s). Paths must not be empty, begin with " @@ -132,7 +132,7 @@ FieldPath FieldPath::ParseServerFormat(const std::string& path) { } std::string FieldPath::CanonicalString() const { - return absl::StrJoin(begin(), end(), '.', + return absl::StrJoin(begin(), end(), ".", [](std::string* out, const std::string& segment) { out->append(EscapedSegment(segment)); }); @@ -140,7 +140,6 @@ std::string FieldPath::CanonicalString() const { // OBC: do we really need emptypath? // OBC: do we really need *shared* keypath? -}; bool operator<(const FieldPath& lhs, const FieldPath& rhs) { return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 6a2c3a1584a..f3c2ab65567 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -19,6 +19,7 @@ #include #include +#include #include "Firestore/core/src/firebase/firestore/model/base_path.h" @@ -40,6 +41,8 @@ class FieldPath : public impl::BasePath { // OBC: do we really need emptypath? // OBC: do we really need *shared* keypath? + private: + FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} {} }; bool operator<(const FieldPath& lhs, const FieldPath& rhs); diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index 7fb0afa2e52..00cd71c555a 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -39,7 +39,8 @@ ResourcePath ResourcePath::Parse(const std::string& path) { // SkipEmpty because we may still have an empty segment at the beginning or // end if they had a leading or trailing slash (which we allow). - auto segments = absl::StrSplit(path, '/', absl::SkipEmpty()); + std::vector segments = + absl::StrSplit(path, '/', absl::SkipEmpty()); return ResourcePath{std::move(segments)}; } From e5a07763c6c9b70d1da51794d5d51fa657828688 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 14:46:42 -0500 Subject: [PATCH 11/48] constructor test --- .../firestore/model/base_path_test.cc | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index 78e5eea8ee7..d4c3787248e 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -16,6 +16,8 @@ #include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include +#include #include #include "gtest/gtest.h" @@ -24,11 +26,33 @@ namespace firebase { namespace firestore { namespace model { +// A simple struct to be able to instantiate BasePath. struct Path : impl::BasePath { + Path() = default; + template + Path(const IterT begin, const IterT end) : BasePath{begin, end} { + } + Path(std::initializer_list list) : BasePath{list} { + } }; TEST(BasePath, Constructor) { - const Path path; + const Path empty_path; + EXPECT_TRUE(empty_path.empty()); + EXPECT_EQ(0, empty_path.size()); + EXPECT_TRUE(empty_path.begin() == empty_path.end()); + + const Path path_from_list{{"rooms", "Eros", "messages"}}; + EXPECT_FALSE(path_from_list.empty()); + EXPECT_EQ(3, path_from_list.size()); + EXPECT_TRUE(path_from_list.begin() + 3 == path_from_list.end()); + + std::vector segments{"rooms", "Eros", "messages"}; + const Path path_from_segments{segments.begin(), segments.end()}; + EXPECT_FALSE(path_from_segments.empty()); + EXPECT_EQ(3, path_from_segments.size()); + EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); + // EXPECT_EQ(0, timestamp_zero.seconds()); } From 7b480b522335ce18eefc9dc036a0f873e2c39d3d Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 15:39:05 -0500 Subject: [PATCH 12/48] add and use shorter assert macro --- .../src/firebase/firestore/model/base_path.h | 22 +++++++++---------- .../firebase/firestore/util/firebase_assert.h | 4 ++++ .../firestore/model/base_path_test.cc | 17 ++++++++++++-- .../abseil-cpp/absl/strings/CMakeLists.txt | 2 -- .../abseil-cpp/absl/strings/str_join.cc | 1 - .../abseil-cpp/absl/strings/strip.cc | 1 - 6 files changed, 29 insertions(+), 18 deletions(-) delete mode 100644 Firestore/third_party/abseil-cpp/absl/strings/str_join.cc delete mode 100644 Firestore/third_party/abseil-cpp/absl/strings/strip.cc diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 28f7b0befd9..cb3eec0a338 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -24,6 +24,8 @@ #include #include +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" @@ -41,24 +43,21 @@ class BasePath { public: using const_iterator = SegmentsT::const_iterator; - const std::string& operator[](const size_t index) const { - return at(index); + const std::string& operator[](const size_t i) const { + return at(i); } const std::string& at(const size_t i) const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(index < segments_.size(), - "index %u out of range", index); + FIREBASE_ASSERT_MESSAGE(i < segments_.size(), "index %u out of range", i); return segments_[i]; } const std::string& front() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), - "Cannot call front on empty path"); + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call front on empty path"); return at(0); } const std::string& back() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(!empty(), - "Cannot call back on empty path"); + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call back on empty path"); return at(size() - 1); } @@ -90,7 +89,7 @@ class BasePath { } T WithoutFirstElements(const size_t count = 1) const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + FIREBASE_ASSERT_MESSAGE( count <= size(), "Cannot call WithoutFirstElements(%u) on path of length %u", count, size()); @@ -98,13 +97,12 @@ class BasePath { } T WithoutLastElement() const { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !empty(), "Cannot call WithoutLastElement() on empty path"); + FIREBASE_ASSERT_MESSAGE(!empty(), + "Cannot call WithoutLastElement() on empty path"); return T{segments_.begin(), segments_.end() - 1}; } bool IsPrefixOf(const T& rhs) const { - // OBC empty range return size() < rhs.size() && std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); } diff --git a/Firestore/core/src/firebase/firestore/util/firebase_assert.h b/Firestore/core/src/firebase/firestore/util/firebase_assert.h index cff550abf2d..993f27a00cf 100644 --- a/Firestore/core/src/firebase/firestore/util/firebase_assert.h +++ b/Firestore/core/src/firebase/firestore/util/firebase_assert.h @@ -75,6 +75,10 @@ } \ } while (0) +// Assert with custom message that is not compiled out in release builds. +#define FIREBASE_ASSERT_MESSAGE(expression, ...) \ + FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION(expression, expression, __VA_ARGS__) + // Assert condition is true otherwise display the specified expression, // message and abort. Compiled out of release builds. #if defined(NDEBUG) diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index d4c3787248e..208947d01cd 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -56,8 +56,21 @@ TEST(BasePath, Constructor) { // EXPECT_EQ(0, timestamp_zero.seconds()); } -// constructor -// indexing +TEST(BasePath, Indexing) { + const Path path{{"rooms", "Eros", "messages"}}; + + EXPECT_EQ(path.front(), "rooms"); + // EXPECT_EQ(path[0], "rooms"); + // EXPECT_EQ(path.at(0), "rooms"); + + // EXPECT_EQ(path[1], "Eros"); + // EXPECT_EQ(path.at(1), "Eros"); + + // EXPECT_EQ(path[2], "messages"); + // EXPECT_EQ(path.at(2), "messages"); + // EXPECT_EQ(path.back(), "messages"); +} + // WithoutFirst // WithoutLast // Concatenated diff --git a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt index d7afd3f86fc..33547785b63 100644 --- a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt +++ b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt @@ -43,9 +43,7 @@ list(APPEND STRINGS_SRC "internal/memutil.cc" "internal/ostringstream.cc" "match.cc" - "strip.cc" "str_cat.cc" - "str_join.cc" "str_replace.cc" "str_split.cc" "string_view.cc" diff --git a/Firestore/third_party/abseil-cpp/absl/strings/str_join.cc b/Firestore/third_party/abseil-cpp/absl/strings/str_join.cc deleted file mode 100644 index 929c05d7236..00000000000 --- a/Firestore/third_party/abseil-cpp/absl/strings/str_join.cc +++ /dev/null @@ -1 +0,0 @@ -404: Not Found diff --git a/Firestore/third_party/abseil-cpp/absl/strings/strip.cc b/Firestore/third_party/abseil-cpp/absl/strings/strip.cc deleted file mode 100644 index 929c05d7236..00000000000 --- a/Firestore/third_party/abseil-cpp/absl/strings/strip.cc +++ /dev/null @@ -1 +0,0 @@ -404: Not Found From e8fa45f0777dbdac57050014cc88fa727217ce41 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 15:39:56 -0500 Subject: [PATCH 13/48] indexing tests succeed --- .../firebase/firestore/model/base_path_test.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index 208947d01cd..3279db5ebdb 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -60,15 +60,15 @@ TEST(BasePath, Indexing) { const Path path{{"rooms", "Eros", "messages"}}; EXPECT_EQ(path.front(), "rooms"); - // EXPECT_EQ(path[0], "rooms"); - // EXPECT_EQ(path.at(0), "rooms"); + EXPECT_EQ(path[0], "rooms"); + EXPECT_EQ(path.at(0), "rooms"); - // EXPECT_EQ(path[1], "Eros"); - // EXPECT_EQ(path.at(1), "Eros"); + EXPECT_EQ(path[1], "Eros"); + EXPECT_EQ(path.at(1), "Eros"); - // EXPECT_EQ(path[2], "messages"); - // EXPECT_EQ(path.at(2), "messages"); - // EXPECT_EQ(path.back(), "messages"); + EXPECT_EQ(path[2], "messages"); + EXPECT_EQ(path.at(2), "messages"); + EXPECT_EQ(path.back(), "messages"); } // WithoutFirst From 608bcf022a1c92bcf078afd27ad33c4ca3f241fc Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 17:43:55 -0500 Subject: [PATCH 14/48] withoutfirst test works --- .../src/firebase/firestore/model/base_path.h | 13 ++++++++--- .../firestore/model/base_path_test.cc | 22 +++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index cb3eec0a338..2e65dae2658 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -88,7 +88,11 @@ class BasePath { return T{std::move(concatenated)}; } - T WithoutFirstElements(const size_t count = 1) const { + T WithoutFirstElement() const { + return WithoutFirstElements(1); + } + + T WithoutFirstElements(const size_t count) const { FIREBASE_ASSERT_MESSAGE( count <= size(), "Cannot call WithoutFirstElements(%u) on path of length %u", count, @@ -107,6 +111,10 @@ class BasePath { std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); } + bool operator==(const BasePath& rhs) const { + return segments_ == rhs.segments_; + } + // std::hash // to_string @@ -115,8 +123,7 @@ class BasePath { template BasePath(const IterT begin, const IterT end) : segments_{begin, end} { } - BasePath(std::initializer_list list) - : segments_{list.begin(), list.end()} { + BasePath(std::initializer_list list) : segments_{list} { } BasePath(SegmentsT&& segments) : segments_{std::move(segments)} { } diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index 3279db5ebdb..bde81c7b002 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -28,12 +28,16 @@ namespace model { // A simple struct to be able to instantiate BasePath. struct Path : impl::BasePath { - Path() = default; + Path() = default; template Path(const IterT begin, const IterT end) : BasePath{begin, end} { } Path(std::initializer_list list) : BasePath{list} { } + + bool operator==(const Path& rhs) const { + return BasePath::operator==(rhs); + } }; TEST(BasePath, Constructor) { @@ -52,8 +56,6 @@ TEST(BasePath, Constructor) { EXPECT_FALSE(path_from_segments.empty()); EXPECT_EQ(3, path_from_segments.size()); EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); - - // EXPECT_EQ(0, timestamp_zero.seconds()); } TEST(BasePath, Indexing) { @@ -71,7 +73,19 @@ TEST(BasePath, Indexing) { EXPECT_EQ(path.back(), "messages"); } -// WithoutFirst +TEST(BasePath, WithoutFirst) { + const Path abc{"rooms", "Eros", "messages"}; + const Path bc = {"Eros", "messages"}; + const Path c{"messages"}; + const Path empty; + const Path abc_dupl{"rooms", "Eros", "messages"}; + + EXPECT_EQ(bc, abc.WithoutFirstElement()); + EXPECT_EQ(c, abc.WithoutFirstElements(2)); + EXPECT_EQ(empty, abc.WithoutFirstElements(3)); + EXPECT_EQ(abc_dupl, abc); +} + // WithoutLast // Concatenated // < From fa5aa94ea98c4e620ce2371997c7b25aae91085d Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 17:46:01 -0500 Subject: [PATCH 15/48] withoutlast test works --- .../firebase/firestore/model/base_path_test.cc | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index bde81c7b002..3c96437a578 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -28,7 +28,7 @@ namespace model { // A simple struct to be able to instantiate BasePath. struct Path : impl::BasePath { - Path() = default; + Path() = default; template Path(const IterT begin, const IterT end) : BasePath{begin, end} { } @@ -86,7 +86,19 @@ TEST(BasePath, WithoutFirst) { EXPECT_EQ(abc_dupl, abc); } -// WithoutLast +TEST(BasePath, WithoutLast) { + const Path abc{"rooms", "Eros", "messages"}; + const Path ab = {"rooms", "Eros"}; + const Path a{"rooms"}; + const Path empty; + const Path abc_dupl{"rooms", "Eros", "messages"}; + + EXPECT_EQ(ab, abc.WithoutLastElement()); + EXPECT_EQ(a, abc.WithoutLastElement().WithoutLastElement()); + EXPECT_EQ(empty, + abc.WithoutLastElement().WithoutLastElement().WithoutLastElement()); +} + // Concatenated // < // isPrefixOf From 42c106c715d9f7bf913a1ea0ec7acc870748044e Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 17:55:26 -0500 Subject: [PATCH 16/48] concatenation test works --- .../src/firebase/firestore/model/base_path.h | 3 +++ .../firestore/model/base_path_test.cc | 24 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 2e65dae2658..4d97ab2e899 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -114,6 +114,9 @@ class BasePath { bool operator==(const BasePath& rhs) const { return segments_ == rhs.segments_; } + bool operator!=(const BasePath& rhs) const { + return segments_ != rhs.segments_; + } // std::hash // to_string diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index 3c96437a578..a785574ba4d 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -34,10 +34,14 @@ struct Path : impl::BasePath { } Path(std::initializer_list list) : BasePath{list} { } + Path(SegmentsT&& segments) : BasePath{std::move(segments)} {} bool operator==(const Path& rhs) const { return BasePath::operator==(rhs); } + bool operator!=(const Path& rhs) const { + return BasePath::operator!=(rhs); + } }; TEST(BasePath, Constructor) { @@ -75,11 +79,15 @@ TEST(BasePath, Indexing) { TEST(BasePath, WithoutFirst) { const Path abc{"rooms", "Eros", "messages"}; - const Path bc = {"Eros", "messages"}; + const Path bc{"Eros", "messages"}; const Path c{"messages"}; const Path empty; const Path abc_dupl{"rooms", "Eros", "messages"}; + EXPECT_NE(empty, c); + EXPECT_NE(c, bc); + EXPECT_NE(bc, abc); + EXPECT_EQ(bc, abc.WithoutFirstElement()); EXPECT_EQ(c, abc.WithoutFirstElements(2)); EXPECT_EQ(empty, abc.WithoutFirstElements(3)); @@ -88,7 +96,7 @@ TEST(BasePath, WithoutFirst) { TEST(BasePath, WithoutLast) { const Path abc{"rooms", "Eros", "messages"}; - const Path ab = {"rooms", "Eros"}; + const Path ab{"rooms", "Eros"}; const Path a{"rooms"}; const Path empty; const Path abc_dupl{"rooms", "Eros", "messages"}; @@ -99,6 +107,18 @@ TEST(BasePath, WithoutLast) { abc.WithoutLastElement().WithoutLastElement().WithoutLastElement()); } +TEST(BasePath, Concatenation) { + const Path path; + const Path a{"rooms"}; + const Path ab{"rooms", "Eros"}; + const Path abc{"rooms", "Eros", "messages"}; + + EXPECT_EQ(a, path.Concatenated("rooms")); + EXPECT_EQ(ab, path.Concatenated("rooms").Concatenated("Eros")); + EXPECT_EQ(abc, path.Concatenated("rooms").Concatenated("Eros").Concatenated("messages")); + EXPECT_EQ(abc, path.Concatenated(Path{"rooms", "Eros", "messages"})); +} + // Concatenated // < // isPrefixOf From 0d89120a3664b3df71208b59ca6bbc13c76e8384 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 18:01:16 -0500 Subject: [PATCH 17/48] comparison test works --- .../src/firebase/firestore/model/base_path.h | 6 ++++ .../firestore/model/base_path_test.cc | 29 +++++++++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 4d97ab2e899..c1b5d69e308 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -117,6 +117,12 @@ class BasePath { bool operator!=(const BasePath& rhs) const { return segments_ != rhs.segments_; } + bool operator<(const BasePath& rhs) const { + return segments_ < rhs.segments_; + } + bool operator>(const BasePath& rhs) const { + return segments_ > rhs.segments_; + } // std::hash // to_string diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index a785574ba4d..1a8b5ad5718 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -42,6 +42,12 @@ struct Path : impl::BasePath { bool operator!=(const Path& rhs) const { return BasePath::operator!=(rhs); } + bool operator<(const Path& rhs) const { + return BasePath::operator<(rhs); + } + bool operator>(const Path& rhs) const { + return BasePath::operator>(rhs); + } }; TEST(BasePath, Constructor) { @@ -119,8 +125,27 @@ TEST(BasePath, Concatenation) { EXPECT_EQ(abc, path.Concatenated(Path{"rooms", "Eros", "messages"})); } -// Concatenated -// < +TEST(BasePath, Comparison) { + const Path abc{"a", "b", "c"}; + const Path abc2{"a", "b", "c"}; + const Path xyz{"x", "y", "z"}; + EXPECT_EQ(abc, abc2); + EXPECT_NE(abc, xyz); + + const Path empty; + const Path a{"a"}; + const Path b{"b"}; + const Path ab{"a", "b"}; + + EXPECT_TRUE(empty < a); + EXPECT_TRUE(a < b); + EXPECT_TRUE(a < ab); + + EXPECT_TRUE(a > empty); + EXPECT_TRUE(b > a); + EXPECT_TRUE(ab > a); +} + // isPrefixOf // throws on invalid From 53e5564104a7265b6b0f46777e8f875e186133c0 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 18:09:20 -0500 Subject: [PATCH 18/48] isprefixof test works --- .../src/firebase/firestore/model/base_path.h | 3 +- .../firestore/model/base_path_test.cc | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index c1b5d69e308..8818f56a47f 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -107,8 +107,7 @@ class BasePath { } bool IsPrefixOf(const T& rhs) const { - return size() < rhs.size() && - std::equal(begin(), end(), rhs.begin(), rhs.begin() + size()); + return size() <= rhs.size() && std::equal(begin(), end(), rhs.begin()); } bool operator==(const BasePath& rhs) const { diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index 1a8b5ad5718..5a40ecb009d 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -146,7 +146,42 @@ TEST(BasePath, Comparison) { EXPECT_TRUE(ab > a); } -// isPrefixOf +TEST(BasePath, IsPrefixOf) { + const Path empty; + const Path a{"a"}; + const Path ab{"a", "b"}; + const Path abc{"a", "b", "c"}; + const Path b{"b"}; + const Path ba{"b", "a"}; + + EXPECT_TRUE(empty.IsPrefixOf(empty)); + EXPECT_TRUE(empty.IsPrefixOf(a)); + EXPECT_TRUE(empty.IsPrefixOf(ab)); + EXPECT_TRUE(empty.IsPrefixOf(abc)); + EXPECT_TRUE(empty.IsPrefixOf(b)); + EXPECT_TRUE(empty.IsPrefixOf(ba)); + + EXPECT_FALSE(a.IsPrefixOf(empty)); + EXPECT_TRUE(a.IsPrefixOf(a)); + EXPECT_TRUE(a.IsPrefixOf(ab)); + EXPECT_TRUE(a.IsPrefixOf(abc)); + EXPECT_FALSE(a.IsPrefixOf(b)); + EXPECT_FALSE(a.IsPrefixOf(ba)); + + EXPECT_FALSE(ab.IsPrefixOf(empty)); + EXPECT_FALSE(ab.IsPrefixOf(a)); + EXPECT_TRUE(ab.IsPrefixOf(ab)); + EXPECT_TRUE(ab.IsPrefixOf(abc)); + EXPECT_FALSE(ab.IsPrefixOf(b)); + EXPECT_FALSE(ab.IsPrefixOf(ba)); + + EXPECT_FALSE(abc.IsPrefixOf(empty)); + EXPECT_FALSE(abc.IsPrefixOf(a)); + EXPECT_FALSE(abc.IsPrefixOf(ab)); + EXPECT_TRUE(abc.IsPrefixOf(abc)); + EXPECT_FALSE(abc.IsPrefixOf(b)); + EXPECT_FALSE(abc.IsPrefixOf(ba)); +} // throws on invalid // canonical string From e58bb4916a73903419b5221e2136ddd811bade19 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 18:23:33 -0500 Subject: [PATCH 19/48] test for failures --- .../firestore/model/base_path_test.cc | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/base_path_test.cc index 5a40ecb009d..33cdd07fc89 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/base_path_test.cc @@ -34,7 +34,8 @@ struct Path : impl::BasePath { } Path(std::initializer_list list) : BasePath{list} { } - Path(SegmentsT&& segments) : BasePath{std::move(segments)} {} + Path(SegmentsT&& segments) : BasePath{std::move(segments)} { + } bool operator==(const Path& rhs) const { return BasePath::operator==(rhs); @@ -121,8 +122,12 @@ TEST(BasePath, Concatenation) { EXPECT_EQ(a, path.Concatenated("rooms")); EXPECT_EQ(ab, path.Concatenated("rooms").Concatenated("Eros")); - EXPECT_EQ(abc, path.Concatenated("rooms").Concatenated("Eros").Concatenated("messages")); + EXPECT_EQ(abc, path.Concatenated("rooms").Concatenated("Eros").Concatenated( + "messages")); EXPECT_EQ(abc, path.Concatenated(Path{"rooms", "Eros", "messages"})); + + const Path bcd{"Eros", "messages", "this_week"}; + EXPECT_EQ(bcd, abc.WithoutFirstElement().Concatenated("this_week")); } TEST(BasePath, Comparison) { @@ -183,7 +188,18 @@ TEST(BasePath, IsPrefixOf) { EXPECT_FALSE(abc.IsPrefixOf(ba)); } -// throws on invalid +TEST(BasePath, Failures) { + const Path path; + ASSERT_DEATH_IF_SUPPORTED(path.front(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.back(), ""); + ASSERT_DEATH_IF_SUPPORTED(path[0], ""); + ASSERT_DEATH_IF_SUPPORTED(path[1], ""); + ASSERT_DEATH_IF_SUPPORTED(path.at(0), ""); + ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElement(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElements(2), ""); + ASSERT_DEATH_IF_SUPPORTED(path.WithoutLastElement(), ""); +} + // canonical string // --//-- of substr? From 7bb768e138750684d6743839dcb2b16f85df6021 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 18:53:47 -0500 Subject: [PATCH 20/48] replace base path tests with field path tests --- .../Firestore.xcodeproj/project.pbxproj | 8 +- .../src/firebase/firestore/model/field_path.h | 19 ++- .../firebase/firestore/model/CMakeLists.txt | 2 +- .../{base_path_test.cc => field_path_test.cc} | 128 ++++++++---------- 4 files changed, 80 insertions(+), 77 deletions(-) rename Firestore/core/test/firebase/firestore/model/{base_path_test.cc => field_path_test.cc} (62%) diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index e3a49365661..be988255e62 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -82,7 +82,7 @@ ABF341051FE860CA00C48322 /* FSTAPIHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ABF341021FE8593500C48322 /* FSTAPIHelpers.m */; }; ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; - B69E7F122023985A003C7930 /* base_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B69E7F1020239852003C7930 /* base_path_test.cc */; }; + B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; }; D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; }; D5B25474286C9800CE42B8C2 /* FSTTestDispatchQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */; }; @@ -271,7 +271,7 @@ ABF341021FE8593500C48322 /* FSTAPIHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FSTAPIHelpers.m; sourceTree = ""; }; ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B69E7F1020239852003C7930 /* base_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = base_path_test.cc; sourceTree = ""; }; + B686F2AD2023DDB20028D6BE /* field_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_path_test.cc; sourceTree = ""; }; CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = ""; }; D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSTTestDispatchQueue.m; sourceTree = ""; }; @@ -555,7 +555,7 @@ AB356EF5200E9D1A0089B766 /* model */ = { isa = PBXGroup; children = ( - B69E7F1020239852003C7930 /* base_path_test.cc */, + B686F2AD2023DDB20028D6BE /* field_path_test.cc */, AB356EF6200EA5EB0089B766 /* field_value_test.cc */, ABF6506B201131F8005F2C74 /* timestamp_test.cc */, AB71064B201FA60300344F18 /* database_id_test.cc */, @@ -1247,13 +1247,13 @@ AB9945281FE2DE0C00DFC1E6 /* FIRSnapshotMetadataTests.m in Sources */, 5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */, DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */, + B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */, DE51B1DE1F0D490D0013853F /* FSTMemoryLocalStoreTests.m in Sources */, DE51B1EC1F0D49140013853F /* FSTDatabaseIDTests.m in Sources */, DE51B1ED1F0D49140013853F /* FSTDocumentKeyTests.m in Sources */, DE51B1D41F0D48CD0013853F /* FSTViewTests.m in Sources */, 54740A581FC914F000713A1A /* autoid_test.cc in Sources */, DE51B1F41F0D491B0013853F /* FSTRemoteEventTests.m in Sources */, - B69E7F122023985A003C7930 /* base_path_test.cc in Sources */, 548DB927200D590300E00ABC /* assert_test.cc in Sources */, 54E928241F33953300C1953E /* FSTEventAccumulator.m in Sources */, 5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */, diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index f3c2ab65567..2e01c822eef 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -39,14 +39,29 @@ class FieldPath : public impl::BasePath { std::string CanonicalString() const; + bool operator==(const FieldPath& rhs) const { + return BasePath::operator==(rhs); + } + bool operator!=(const FieldPath& rhs) const { + return BasePath::operator!=(rhs); + } + bool operator<(const FieldPath& rhs) const { + return BasePath::operator<(rhs); + } + bool operator>(const FieldPath& rhs) const { + return BasePath::operator>(rhs); + } + // OBC: do we really need emptypath? // OBC: do we really need *shared* keypath? private: FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} {} + + friend class BasePath; }; -bool operator<(const FieldPath& lhs, const FieldPath& rhs); -bool operator==(const FieldPath& lhs, const FieldPath& rhs); +// bool operator<(const FieldPath& lhs, const FieldPath& rhs); +// bool operator==(const FieldPath& lhs, const FieldPath& rhs); } // namespace model } // namespace firestore diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 81c91666705..ac01b5eb7bf 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -15,8 +15,8 @@ cc_test( firebase_firestore_model_test SOURCES - base_path_test.cc database_id_test.cc + field_path_test.cc field_value_test.cc timestamp_test.cc DEPENDS diff --git a/Firestore/core/test/firebase/firestore/model/base_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc similarity index 62% rename from Firestore/core/test/firebase/firestore/model/base_path_test.cc rename to Firestore/core/test/firebase/firestore/model/field_path_test.cc index 33cdd07fc89..1e93c5a0826 100644 --- a/Firestore/core/test/firebase/firestore/model/base_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "Firestore/core/src/firebase/firestore/model/field_path.h" #include #include @@ -26,51 +26,26 @@ namespace firebase { namespace firestore { namespace model { -// A simple struct to be able to instantiate BasePath. -struct Path : impl::BasePath { - Path() = default; - template - Path(const IterT begin, const IterT end) : BasePath{begin, end} { - } - Path(std::initializer_list list) : BasePath{list} { - } - Path(SegmentsT&& segments) : BasePath{std::move(segments)} { - } - - bool operator==(const Path& rhs) const { - return BasePath::operator==(rhs); - } - bool operator!=(const Path& rhs) const { - return BasePath::operator!=(rhs); - } - bool operator<(const Path& rhs) const { - return BasePath::operator<(rhs); - } - bool operator>(const Path& rhs) const { - return BasePath::operator>(rhs); - } -}; - -TEST(BasePath, Constructor) { - const Path empty_path; +TEST(FieldPath, Constructor) { + const FieldPath empty_path; EXPECT_TRUE(empty_path.empty()); EXPECT_EQ(0, empty_path.size()); EXPECT_TRUE(empty_path.begin() == empty_path.end()); - const Path path_from_list{{"rooms", "Eros", "messages"}}; + const FieldPath path_from_list{{"rooms", "Eros", "messages"}}; EXPECT_FALSE(path_from_list.empty()); EXPECT_EQ(3, path_from_list.size()); EXPECT_TRUE(path_from_list.begin() + 3 == path_from_list.end()); std::vector segments{"rooms", "Eros", "messages"}; - const Path path_from_segments{segments.begin(), segments.end()}; + const FieldPath path_from_segments{segments.begin(), segments.end()}; EXPECT_FALSE(path_from_segments.empty()); EXPECT_EQ(3, path_from_segments.size()); EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); } -TEST(BasePath, Indexing) { - const Path path{{"rooms", "Eros", "messages"}}; +TEST(FieldPath, Indexing) { + const FieldPath path{{"rooms", "Eros", "messages"}}; EXPECT_EQ(path.front(), "rooms"); EXPECT_EQ(path[0], "rooms"); @@ -84,12 +59,12 @@ TEST(BasePath, Indexing) { EXPECT_EQ(path.back(), "messages"); } -TEST(BasePath, WithoutFirst) { - const Path abc{"rooms", "Eros", "messages"}; - const Path bc{"Eros", "messages"}; - const Path c{"messages"}; - const Path empty; - const Path abc_dupl{"rooms", "Eros", "messages"}; +TEST(FieldPath, WithoutFirst) { + const FieldPath abc{"rooms", "Eros", "messages"}; + const FieldPath bc{"Eros", "messages"}; + const FieldPath c{"messages"}; + const FieldPath empty; + const FieldPath abc_dupl{"rooms", "Eros", "messages"}; EXPECT_NE(empty, c); EXPECT_NE(c, bc); @@ -101,12 +76,12 @@ TEST(BasePath, WithoutFirst) { EXPECT_EQ(abc_dupl, abc); } -TEST(BasePath, WithoutLast) { - const Path abc{"rooms", "Eros", "messages"}; - const Path ab{"rooms", "Eros"}; - const Path a{"rooms"}; - const Path empty; - const Path abc_dupl{"rooms", "Eros", "messages"}; +TEST(FieldPath, WithoutLast) { + const FieldPath abc{"rooms", "Eros", "messages"}; + const FieldPath ab{"rooms", "Eros"}; + const FieldPath a{"rooms"}; + const FieldPath empty; + const FieldPath abc_dupl{"rooms", "Eros", "messages"}; EXPECT_EQ(ab, abc.WithoutLastElement()); EXPECT_EQ(a, abc.WithoutLastElement().WithoutLastElement()); @@ -114,33 +89,33 @@ TEST(BasePath, WithoutLast) { abc.WithoutLastElement().WithoutLastElement().WithoutLastElement()); } -TEST(BasePath, Concatenation) { - const Path path; - const Path a{"rooms"}; - const Path ab{"rooms", "Eros"}; - const Path abc{"rooms", "Eros", "messages"}; +TEST(FieldPath, Concatenation) { + const FieldPath path; + const FieldPath a{"rooms"}; + const FieldPath ab{"rooms", "Eros"}; + const FieldPath abc{"rooms", "Eros", "messages"}; EXPECT_EQ(a, path.Concatenated("rooms")); EXPECT_EQ(ab, path.Concatenated("rooms").Concatenated("Eros")); EXPECT_EQ(abc, path.Concatenated("rooms").Concatenated("Eros").Concatenated( "messages")); - EXPECT_EQ(abc, path.Concatenated(Path{"rooms", "Eros", "messages"})); + EXPECT_EQ(abc, path.Concatenated(FieldPath{"rooms", "Eros", "messages"})); - const Path bcd{"Eros", "messages", "this_week"}; + const FieldPath bcd{"Eros", "messages", "this_week"}; EXPECT_EQ(bcd, abc.WithoutFirstElement().Concatenated("this_week")); } -TEST(BasePath, Comparison) { - const Path abc{"a", "b", "c"}; - const Path abc2{"a", "b", "c"}; - const Path xyz{"x", "y", "z"}; +TEST(FieldPath, Comparison) { + const FieldPath abc{"a", "b", "c"}; + const FieldPath abc2{"a", "b", "c"}; + const FieldPath xyz{"x", "y", "z"}; EXPECT_EQ(abc, abc2); EXPECT_NE(abc, xyz); - const Path empty; - const Path a{"a"}; - const Path b{"b"}; - const Path ab{"a", "b"}; + const FieldPath empty; + const FieldPath a{"a"}; + const FieldPath b{"b"}; + const FieldPath ab{"a", "b"}; EXPECT_TRUE(empty < a); EXPECT_TRUE(a < b); @@ -151,13 +126,13 @@ TEST(BasePath, Comparison) { EXPECT_TRUE(ab > a); } -TEST(BasePath, IsPrefixOf) { - const Path empty; - const Path a{"a"}; - const Path ab{"a", "b"}; - const Path abc{"a", "b", "c"}; - const Path b{"b"}; - const Path ba{"b", "a"}; +TEST(FieldPath, IsPrefixOf) { + const FieldPath empty; + const FieldPath a{"a"}; + const FieldPath ab{"a", "b"}; + const FieldPath abc{"a", "b", "c"}; + const FieldPath b{"b"}; + const FieldPath ba{"b", "a"}; EXPECT_TRUE(empty.IsPrefixOf(empty)); EXPECT_TRUE(empty.IsPrefixOf(a)); @@ -188,8 +163,8 @@ TEST(BasePath, IsPrefixOf) { EXPECT_FALSE(abc.IsPrefixOf(ba)); } -TEST(BasePath, Failures) { - const Path path; +TEST(FieldPath, AccessFailures) { + const FieldPath path; ASSERT_DEATH_IF_SUPPORTED(path.front(), ""); ASSERT_DEATH_IF_SUPPORTED(path.back(), ""); ASSERT_DEATH_IF_SUPPORTED(path[0], ""); @@ -200,8 +175,21 @@ TEST(BasePath, Failures) { ASSERT_DEATH_IF_SUPPORTED(path.WithoutLastElement(), ""); } -// canonical string -// --//-- of substr? +TEST(FieldPath, Parsing) { + EXPECT_EQ(FieldPath{"foo"}, FieldPath::ParseServerFormat("foo")); +} + +TEST(FieldPath, ParseFailures) { + // const FieldPath path; + // ASSERT_DEATH_IF_SUPPORTED(path.front(), ""); + // ASSERT_DEATH_IF_SUPPORTED(path.back(), ""); + // ASSERT_DEATH_IF_SUPPORTED(path[0], ""); + // ASSERT_DEATH_IF_SUPPORTED(path[1], ""); + // ASSERT_DEATH_IF_SUPPORTED(path.at(0), ""); + // ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElement(), ""); + // ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElements(2), ""); + // ASSERT_DEATH_IF_SUPPORTED(path.WithoutLastElement(), ""); +} } // namespace model } // namespace firestore From 60317fa3b2b8da88480742a21efa843f0bded292 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 19:06:30 -0500 Subject: [PATCH 21/48] test parsing (found a few bugs) --- .../src/firebase/firestore/model/field_path.cc | 18 +++++++++--------- .../firestore/model/field_path_test.cc | 10 ++++++++++ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 6c8d90c5ba4..6e2621617aa 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -86,7 +86,6 @@ FieldPath FieldPath::ParseServerFormat(const std::string& path) { bool insideBackticks = false; // Whether to treat '\' literally or as an escape character. bool escapedCharacter = false; - for (const char c : path) { if (c == '\0') { break; @@ -119,6 +118,7 @@ FieldPath FieldPath::ParseServerFormat(const std::string& path) { break; } } + finish_segment(); FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( !insideBackticks, "Unterminated ` in path %s", path.c_str()); @@ -141,15 +141,15 @@ std::string FieldPath::CanonicalString() const { // OBC: do we really need emptypath? // OBC: do we really need *shared* keypath? -bool operator<(const FieldPath& lhs, const FieldPath& rhs) { -return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), - rhs.end()); -} +// bool operator<(const FieldPath& lhs, const FieldPath& rhs) { +// return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), +// rhs.end()); +// } -bool operator==(const FieldPath& lhs, const FieldPath& rhs) { -return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), - rhs.end()); -} +// bool operator==(const FieldPath& lhs, const FieldPath& rhs) { +// return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), +// rhs.end()); +// } } // namespace model } // namespace firestore diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 1e93c5a0826..24ebb278af0 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -177,6 +177,16 @@ TEST(FieldPath, AccessFailures) { TEST(FieldPath, Parsing) { EXPECT_EQ(FieldPath{"foo"}, FieldPath::ParseServerFormat("foo")); + const FieldPath foo_bar{"foo", "bar"}; + EXPECT_EQ(foo_bar, FieldPath::ParseServerFormat("foo.bar")); + const FieldPath foo_bar_baz{"foo", "bar", "baz"}; + EXPECT_EQ(foo_bar_baz, FieldPath::ParseServerFormat("foo.bar.baz")); + const FieldPath foo_slash{".foo\\"}; + EXPECT_EQ(foo_slash, FieldPath::ParseServerFormat("`.foo\\\\`")); + const FieldPath foo_slash_foo{".foo\\", ".foo"}; + EXPECT_EQ(foo_slash_foo, FieldPath::ParseServerFormat("`.foo\\\\`.`.foo`")); + const FieldPath foo_tilde_bar{"foo", "`", "bar"}; + EXPECT_EQ(foo_tilde_bar, FieldPath::ParseServerFormat("foo.`\\``.bar")); } TEST(FieldPath, ParseFailures) { From 3a7c97e93e54414d2860a2930b1bd37e490d22fd Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 19:09:45 -0500 Subject: [PATCH 22/48] use raw string literals --- .../test/firebase/firestore/model/field_path_test.cc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 24ebb278af0..e6a81261678 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -181,12 +181,12 @@ TEST(FieldPath, Parsing) { EXPECT_EQ(foo_bar, FieldPath::ParseServerFormat("foo.bar")); const FieldPath foo_bar_baz{"foo", "bar", "baz"}; EXPECT_EQ(foo_bar_baz, FieldPath::ParseServerFormat("foo.bar.baz")); - const FieldPath foo_slash{".foo\\"}; - EXPECT_EQ(foo_slash, FieldPath::ParseServerFormat("`.foo\\\\`")); - const FieldPath foo_slash_foo{".foo\\", ".foo"}; - EXPECT_EQ(foo_slash_foo, FieldPath::ParseServerFormat("`.foo\\\\`.`.foo`")); + const FieldPath foo_slash{R"(.foo\)"}; + EXPECT_EQ(foo_slash, FieldPath::ParseServerFormat(R"(`.foo\\`)")); + const FieldPath foo_slash_foo{R"(.foo\)", ".foo"}; + EXPECT_EQ(foo_slash_foo, FieldPath::ParseServerFormat(R"(`.foo\\`.`.foo`)")); const FieldPath foo_tilde_bar{"foo", "`", "bar"}; - EXPECT_EQ(foo_tilde_bar, FieldPath::ParseServerFormat("foo.`\\``.bar")); + EXPECT_EQ(foo_tilde_bar, FieldPath::ParseServerFormat(R"(foo.`\``.bar)")); } TEST(FieldPath, ParseFailures) { From 62fdfcb0e3d1fdc2942b6982a866f6ca13187197 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Thu, 1 Feb 2018 19:12:46 -0500 Subject: [PATCH 23/48] add todos, make vector in base const --- Firestore/core/src/firebase/firestore/model/base_path.h | 2 +- .../test/firebase/firestore/model/field_path_test.cc | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 8818f56a47f..e40c34ae81f 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -138,7 +138,7 @@ class BasePath { ~BasePath() = default; private: - SegmentsT segments_; + const SegmentsT segments_; }; } // namespace impl diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index e6a81261678..9615d7ae3e1 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -175,6 +175,15 @@ TEST(FieldPath, AccessFailures) { ASSERT_DEATH_IF_SUPPORTED(path.WithoutLastElement(), ""); } +// DIVE IN: +// canonical string/roundtrip +// parse failures +// concat/skip gives expected canonical string +// SKIP +// copy/move constructor +// +// resourcepathtest + TEST(FieldPath, Parsing) { EXPECT_EQ(FieldPath{"foo"}, FieldPath::ParseServerFormat("foo")); const FieldPath foo_bar{"foo", "bar"}; From 822ef1d03961e8c3849c087db26cc084564219ed Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 12:13:02 -0500 Subject: [PATCH 24/48] parse test - expect round trip --- .../firestore/model/field_path_test.cc | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 9615d7ae3e1..0bcef0d1e2f 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -176,26 +176,31 @@ TEST(FieldPath, AccessFailures) { } // DIVE IN: -// canonical string/roundtrip // parse failures // concat/skip gives expected canonical string // SKIP // copy/move constructor // // resourcepathtest +// +// port comments +// +// PUBLIC CLASS TEST(FieldPath, Parsing) { - EXPECT_EQ(FieldPath{"foo"}, FieldPath::ParseServerFormat("foo")); - const FieldPath foo_bar{"foo", "bar"}; - EXPECT_EQ(foo_bar, FieldPath::ParseServerFormat("foo.bar")); - const FieldPath foo_bar_baz{"foo", "bar", "baz"}; - EXPECT_EQ(foo_bar_baz, FieldPath::ParseServerFormat("foo.bar.baz")); - const FieldPath foo_slash{R"(.foo\)"}; - EXPECT_EQ(foo_slash, FieldPath::ParseServerFormat(R"(`.foo\\`)")); - const FieldPath foo_slash_foo{R"(.foo\)", ".foo"}; - EXPECT_EQ(foo_slash_foo, FieldPath::ParseServerFormat(R"(`.foo\\`.`.foo`)")); - const FieldPath foo_tilde_bar{"foo", "`", "bar"}; - EXPECT_EQ(foo_tilde_bar, FieldPath::ParseServerFormat(R"(foo.`\``.bar)")); + const auto expect_round_trip = [](const std::string& str, + const size_t expected_segments) { + const auto path = FieldPath::ParseServerFormat(str); + EXPECT_EQ(str, path.CanonicalString()); + EXPECT_EQ(expected_segments, path.size()); + }; + + expect_round_trip("foo", 1); + expect_round_trip("foo.bar", 2); + expect_round_trip("foo.bar.baz", 3); + expect_round_trip(R"(`.foo\\`)", 1); + expect_round_trip(R"(`.foo\\`.`.foo`)", 2); + expect_round_trip(R"(foo.`\``.bar)", 3); } TEST(FieldPath, ParseFailures) { From 4e8aab485889582c0dffb7c1ebcbb68bb755a139 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 15:19:15 -0500 Subject: [PATCH 25/48] more parsing tests, and the escaped dot is probably a bug --- Firestore/Example/Tests/Model/FSTPathTests.m | 4 + .../src/firebase/firestore/model/base_path.h | 22 +-- .../firebase/firestore/model/field_path.cc | 28 +-- .../src/firebase/firestore/model/field_path.h | 4 +- .../firebase/firestore/model/resource_path.cc | 7 +- .../firebase/firestore/model/resource_path.h | 8 +- .../firestore/model/field_path_test.cc | 160 ++++++++++++------ 7 files changed, 147 insertions(+), 86 deletions(-) diff --git a/Firestore/Example/Tests/Model/FSTPathTests.m b/Firestore/Example/Tests/Model/FSTPathTests.m index b8529e59691..68bcc44c0b1 100644 --- a/Firestore/Example/Tests/Model/FSTPathTests.m +++ b/Firestore/Example/Tests/Model/FSTPathTests.m @@ -173,6 +173,10 @@ - (void)testCanonicalString { ASSERT_ROUND_TRIP(@"`.foo\\\\`", 1); ASSERT_ROUND_TRIP(@"`.foo\\\\`.`.foo`", 2); ASSERT_ROUND_TRIP(@"foo.`\\``.bar", 3); + + FSTFieldPath *path = [FSTFieldPath pathWithServerFormat:@"foo\\.bar"]; + XCTAssertEqualObjects([path canonicalString], @"`foo.bar`"); + XCTAssertEqual(path.length, 1); } #undef ASSERT_ROUND_TRIP diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index e40c34ae81f..c1c2089dc1b 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -76,33 +76,27 @@ class BasePath { return segments_.end(); } - T Concatenated(const std::string& segment) const { + T Concat(const std::string& segment) const { auto concatenated = segments_; concatenated.push_back(segment); return T{std::move(concatenated)}; } - T Concatenated(const T& path) const { + T Concat(const T& path) const { auto concatenated = segments_; concatenated.insert(concatenated.end(), path.begin(), path.end()); return T{std::move(concatenated)}; } - T WithoutFirstElement() const { - return WithoutFirstElements(1); - } - - T WithoutFirstElements(const size_t count) const { - FIREBASE_ASSERT_MESSAGE( - count <= size(), - "Cannot call WithoutFirstElements(%u) on path of length %u", count, - size()); + T DropFirst(const size_t count = 1) const { + FIREBASE_ASSERT_MESSAGE(count <= size(), + "Cannot call DropFirst(%u) on path of length %u", + count, size()); return T{segments_.begin() + count, segments_.end()}; } - T WithoutLastElement() const { - FIREBASE_ASSERT_MESSAGE(!empty(), - "Cannot call WithoutLastElement() on empty path"); + T DropLast() const { + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call DropLast() on empty path"); return T{segments_.begin(), segments_.end() - 1}; } diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 6e2621617aa..bb7f79f1be2 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -49,7 +49,6 @@ bool IsValidIdentifier(const std::string& segment) { } std::string EscapedSegment(const std::string& segment) { - // OBC dot auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); const bool needs_escaping = !IsValidIdentifier(escaped); if (needs_escaping) { @@ -61,7 +60,7 @@ std::string EscapedSegment(const std::string& segment) { } // namespace -FieldPath FieldPath::ParseServerFormat(const std::string& path) { +FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { // TODO(b/37244157): Once we move to v1beta1, we should make this more // strict. Right now, it allows non-identifier path components, even if they // aren't escaped. Technically, this will mangle paths with backticks in @@ -71,12 +70,15 @@ FieldPath FieldPath::ParseServerFormat(const std::string& path) { std::string segment; segment.reserve(path.size()); - const auto finish_segment = [&segments, &segment, &path] { - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + const auto to_string = [](const absl::string_view view) { + return std::string{view.data(), view.data() + view.size()}; + }; + const auto finish_segment = [&segments, &segment, &path, &to_string] { + FIREBASE_ASSERT_MESSAGE( !segment.empty(), "Invalid field path (%s). Paths must not be empty, begin with " "'.', end with '.', or contain '..'", - path.c_str()); + to_string(path).c_str()); // Move operation will clear segment, but capacity will remain the same // (not strictly speaking required by the standard, but true in practice). segments.push_back(std::move(segment)); @@ -120,22 +122,22 @@ FieldPath FieldPath::ParseServerFormat(const std::string& path) { } finish_segment(); - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !insideBackticks, "Unterminated ` in path %s", path.c_str()); + FIREBASE_ASSERT_MESSAGE(!insideBackticks, "Unterminated ` in path %s", + to_string(path).c_str()); // TODO(b/37244157): Make this a user-facing exception once we // finalize field escaping. - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( - !escapedCharacter, "Trailing escape characters not allowed in %s", - path.c_str()); + FIREBASE_ASSERT_MESSAGE(!escapedCharacter, + "Trailing escape characters not allowed in %s", + to_string(path).c_str()); return FieldPath{std::move(segments)}; } std::string FieldPath::CanonicalString() const { return absl::StrJoin(begin(), end(), ".", - [](std::string* out, const std::string& segment) { - out->append(EscapedSegment(segment)); - }); + [](std::string* out, const std::string& segment) { + out->append(EscapedSegment(segment)); + }); } // OBC: do we really need emptypath? diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 2e01c822eef..3d3fcf0cd3b 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -23,6 +23,8 @@ #include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "absl/strings/string_view.h" + namespace firebase { namespace firestore { namespace model { @@ -35,7 +37,7 @@ class FieldPath : public impl::BasePath { } FieldPath(std::initializer_list list) : BasePath{list} { } - static FieldPath ParseServerFormat(const std::string& path); + static FieldPath ParseServerFormat(absl::string_view path); std::string CanonicalString() const; diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index 00cd71c555a..d85518bfb93 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -28,14 +28,15 @@ namespace firebase { namespace firestore { namespace model { -ResourcePath ResourcePath::Parse(const std::string& path) { +ResourcePath ResourcePath::Parse(const absl::string_view path) { // NOTE: The client is ignorant of any path segments containing escape // sequences (e.g. __id123__) and just passes them through raw (they exist // for legacy reasons and should not be used frequently). - FIREBASE_ASSERT_MESSAGE_WITH_EXPRESSION( + FIREBASE_ASSERT_MESSAGE( path.find("//") == std::string::npos, - "Invalid path (%s). Paths must not contain // in them.", path.c_str()); + "Invalid path (%s). Paths must not contain // in them.", + std::string{path.data(), path.data() + path.size()}.c_str()); // SkipEmpty because we may still have an empty segment at the beginning or // end if they had a leading or trailing slash (which we allow). diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index f8ea5d8504e..4c731860948 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -22,6 +22,8 @@ #include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "absl/strings/string_view.h" + namespace firebase { namespace firestore { namespace model { @@ -34,13 +36,15 @@ class ResourcePath : public impl::BasePath { } ResourcePath(std::initializer_list list) : BasePath{list} { } - static ResourcePath Parse(const std::string& path); + static ResourcePath Parse(absl::string_view path); std::string CanonicalString() const; private: - ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { + ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { } + + friend class BasePath; }; } // namespace model diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 0bcef0d1e2f..008ef8a1c21 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -26,13 +26,13 @@ namespace firebase { namespace firestore { namespace model { -TEST(FieldPath, Constructor) { +TEST(FieldPath, Constructors) { const FieldPath empty_path; EXPECT_TRUE(empty_path.empty()); EXPECT_EQ(0, empty_path.size()); EXPECT_TRUE(empty_path.begin() == empty_path.end()); - const FieldPath path_from_list{{"rooms", "Eros", "messages"}}; + const FieldPath path_from_list = {"rooms", "Eros", "messages"}; EXPECT_FALSE(path_from_list.empty()); EXPECT_EQ(3, path_from_list.size()); EXPECT_TRUE(path_from_list.begin() + 3 == path_from_list.end()); @@ -42,10 +42,16 @@ TEST(FieldPath, Constructor) { EXPECT_FALSE(path_from_segments.empty()); EXPECT_EQ(3, path_from_segments.size()); EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); + + FieldPath copied = path_from_list; + EXPECT_EQ(path_from_list, copied); + const FieldPath moved = std::move(copied); + // Because FieldPath is immutable, move constructor performs a copy. + EXPECT_EQ(copied, moved); } TEST(FieldPath, Indexing) { - const FieldPath path{{"rooms", "Eros", "messages"}}; + const FieldPath path{"rooms", "Eros", "messages"}; EXPECT_EQ(path.front(), "rooms"); EXPECT_EQ(path[0], "rooms"); @@ -59,7 +65,7 @@ TEST(FieldPath, Indexing) { EXPECT_EQ(path.back(), "messages"); } -TEST(FieldPath, WithoutFirst) { +TEST(FieldPath, DropFirst) { const FieldPath abc{"rooms", "Eros", "messages"}; const FieldPath bc{"Eros", "messages"}; const FieldPath c{"messages"}; @@ -70,23 +76,22 @@ TEST(FieldPath, WithoutFirst) { EXPECT_NE(c, bc); EXPECT_NE(bc, abc); - EXPECT_EQ(bc, abc.WithoutFirstElement()); - EXPECT_EQ(c, abc.WithoutFirstElements(2)); - EXPECT_EQ(empty, abc.WithoutFirstElements(3)); + EXPECT_EQ(bc, abc.DropFirst()); + EXPECT_EQ(c, abc.DropFirst(2)); + EXPECT_EQ(empty, abc.DropFirst(3)); EXPECT_EQ(abc_dupl, abc); } -TEST(FieldPath, WithoutLast) { +TEST(FieldPath, DropLast) { const FieldPath abc{"rooms", "Eros", "messages"}; const FieldPath ab{"rooms", "Eros"}; const FieldPath a{"rooms"}; const FieldPath empty; const FieldPath abc_dupl{"rooms", "Eros", "messages"}; - EXPECT_EQ(ab, abc.WithoutLastElement()); - EXPECT_EQ(a, abc.WithoutLastElement().WithoutLastElement()); - EXPECT_EQ(empty, - abc.WithoutLastElement().WithoutLastElement().WithoutLastElement()); + EXPECT_EQ(ab, abc.DropLast()); + EXPECT_EQ(a, abc.DropLast().DropLast()); + EXPECT_EQ(empty, abc.DropLast().DropLast().DropLast()); } TEST(FieldPath, Concatenation) { @@ -95,14 +100,13 @@ TEST(FieldPath, Concatenation) { const FieldPath ab{"rooms", "Eros"}; const FieldPath abc{"rooms", "Eros", "messages"}; - EXPECT_EQ(a, path.Concatenated("rooms")); - EXPECT_EQ(ab, path.Concatenated("rooms").Concatenated("Eros")); - EXPECT_EQ(abc, path.Concatenated("rooms").Concatenated("Eros").Concatenated( - "messages")); - EXPECT_EQ(abc, path.Concatenated(FieldPath{"rooms", "Eros", "messages"})); + EXPECT_EQ(a, path.Concat("rooms")); + EXPECT_EQ(ab, path.Concat("rooms").Concat("Eros")); + EXPECT_EQ(abc, path.Concat("rooms").Concat("Eros").Concat("messages")); + EXPECT_EQ(abc, path.Concat(FieldPath{"rooms", "Eros", "messages"})); const FieldPath bcd{"Eros", "messages", "this_week"}; - EXPECT_EQ(bcd, abc.WithoutFirstElement().Concatenated("this_week")); + EXPECT_EQ(bcd, abc.DropFirst().Concat("this_week")); } TEST(FieldPath, Comparison) { @@ -170,51 +174,101 @@ TEST(FieldPath, AccessFailures) { ASSERT_DEATH_IF_SUPPORTED(path[0], ""); ASSERT_DEATH_IF_SUPPORTED(path[1], ""); ASSERT_DEATH_IF_SUPPORTED(path.at(0), ""); - ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElement(), ""); - ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElements(2), ""); - ASSERT_DEATH_IF_SUPPORTED(path.WithoutLastElement(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.DropFirst(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.DropFirst(2), ""); + ASSERT_DEATH_IF_SUPPORTED(path.DropLast(), ""); } -// DIVE IN: -// parse failures -// concat/skip gives expected canonical string -// SKIP -// copy/move constructor -// -// resourcepathtest -// -// port comments -// -// PUBLIC CLASS - TEST(FieldPath, Parsing) { - const auto expect_round_trip = [](const std::string& str, - const size_t expected_segments) { - const auto path = FieldPath::ParseServerFormat(str); - EXPECT_EQ(str, path.CanonicalString()); - EXPECT_EQ(expected_segments, path.size()); + const auto parse = [](const std::pair expected) { + const auto path = FieldPath::ParseServerFormat(expected.first); + return std::make_pair(path.CanonicalString(), path.size()); + }; + const auto make_expected = [](const std::string& str, const size_t size) { + return std::make_pair(str, size); }; - expect_round_trip("foo", 1); - expect_round_trip("foo.bar", 2); - expect_round_trip("foo.bar.baz", 3); - expect_round_trip(R"(`.foo\\`)", 1); - expect_round_trip(R"(`.foo\\`.`.foo`)", 2); - expect_round_trip(R"(foo.`\``.bar)", 3); + auto expected = make_expected("foo", 1); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected("foo.bar", 2); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected("foo.bar.baz", 3); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected(R"(`.foo\\`)", 1); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected(R"(`.foo\\`.`.foo`)", 2); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected(R"(foo.`\``.bar)", 3); + EXPECT_EQ(expected, parse(expected)); + + // OBC this is probably a bug (behavior equivalent to Obj-C) + const auto path_with_dot = FieldPath::ParseServerFormat(R"(foo\.bar)"); + EXPECT_EQ(path_with_dot.CanonicalString(), "`foo.bar`"); + EXPECT_EQ(path_with_dot.size(), 1); +} + +// This is a special case in C++: std::string may contain embedded nulls. To +// fully mimic behavior of Objective-C code, parsing must terminate upon +// encountering the first null terminator in the string. +TEST(FieldPath, ParseEmbeddedNull) { + std::string str{"foo"}; + str += '\0'; + str += ".bar"; + + const auto path = FieldPath::ParseServerFormat(str); + EXPECT_EQ(path.size(), 1); + EXPECT_EQ(path.CanonicalString(), "foo"); } TEST(FieldPath, ParseFailures) { - // const FieldPath path; - // ASSERT_DEATH_IF_SUPPORTED(path.front(), ""); - // ASSERT_DEATH_IF_SUPPORTED(path.back(), ""); - // ASSERT_DEATH_IF_SUPPORTED(path[0], ""); - // ASSERT_DEATH_IF_SUPPORTED(path[1], ""); - // ASSERT_DEATH_IF_SUPPORTED(path.at(0), ""); - // ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElement(), ""); - // ASSERT_DEATH_IF_SUPPORTED(path.WithoutFirstElements(2), ""); - // ASSERT_DEATH_IF_SUPPORTED(path.WithoutLastElement(), ""); + const auto expect_fail = [](const absl::string_view str) { + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(str), ""); + }; + + expect_fail(""); + expect_fail("."); + expect_fail(".."); + expect_fail("foo."); + expect_fail(".bar"); + expect_fail("foo..bar"); + expect_fail(R"(foo\)"); + expect_fail(R"(foo.\)"); + expect_fail("foo`"); + expect_fail("foo```"); + expect_fail("`foo"); +} + +TEST(FieldPath, CanonicalStringOfSubstring) { + const auto path = FieldPath::ParseServerFormat("foo.bar.baz"); + EXPECT_EQ(path.CanonicalString(), "foo.bar.baz"); + EXPECT_EQ(path.DropFirst().CanonicalString(), "bar.baz"); + EXPECT_EQ(path.DropLast().CanonicalString(), "foo.bar"); + EXPECT_EQ(path.DropFirst().DropLast().CanonicalString(), "bar"); + EXPECT_EQ(path.DropFirst().DropLast().CanonicalString(), "bar"); + EXPECT_EQ(path.DropLast().DropFirst().DropLast().CanonicalString(), ""); +} + +TEST(FieldPath, CanonicalStringEscaping) { + // Should be escaped + EXPECT_EQ(FieldPath::ParseServerFormat("1").CanonicalString(), "`1`"); + EXPECT_EQ(FieldPath::ParseServerFormat("1ab").CanonicalString(), "`1ab`"); + EXPECT_EQ(FieldPath::ParseServerFormat("ab!").CanonicalString(), "`ab!`"); + EXPECT_EQ(FieldPath::ParseServerFormat("/ab").CanonicalString(), "`/ab`"); + EXPECT_EQ(FieldPath::ParseServerFormat("a#b").CanonicalString(), "`a#b`"); + + // Should not be escaped + EXPECT_EQ(FieldPath::ParseServerFormat("_ab").CanonicalString(), "_ab"); + EXPECT_EQ(FieldPath::ParseServerFormat("a1").CanonicalString(), "a1"); + EXPECT_EQ(FieldPath::ParseServerFormat("a_").CanonicalString(), "a_"); } +// TODO: +// empty path, shared key path +// +// port comments +// +// PUBLIC CLASS + } // namespace model } // namespace firestore } // namespace firebase From cf43ba097dc71127d0d2e8b800fc7ce3bc773b4f Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 15:24:21 -0500 Subject: [PATCH 26/48] start with resourcepath tests --- .../Firestore.xcodeproj/project.pbxproj | 4 + .../firebase/firestore/model/CMakeLists.txt | 1 + .../firestore/model/field_path_test.cc | 1 + .../firestore/model/resource_path_test.cc | 81 +++++++++++++++++++ 4 files changed, 87 insertions(+) create mode 100644 Firestore/core/test/firebase/firestore/model/resource_path_test.cc diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index be988255e62..ba02cf20e7b 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; + B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; }; D5B2532E4676014F57A7EAB9 /* FSTStreamTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25C0D4AADFCA3ADB883E4 /* FSTStreamTests.m */; }; D5B25474286C9800CE42B8C2 /* FSTTestDispatchQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */; }; @@ -272,6 +273,7 @@ ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B686F2AD2023DDB20028D6BE /* field_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_path_test.cc; sourceTree = ""; }; + B686F2B02024FFD70028D6BE /* resource_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resource_path_test.cc; sourceTree = ""; }; CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = ""; }; D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; D5B25292CED31B81FDED0411 /* FSTTestDispatchQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FSTTestDispatchQueue.m; sourceTree = ""; }; @@ -555,6 +557,7 @@ AB356EF5200E9D1A0089B766 /* model */ = { isa = PBXGroup; children = ( + B686F2B02024FFD70028D6BE /* resource_path_test.cc */, B686F2AD2023DDB20028D6BE /* field_path_test.cc */, AB356EF6200EA5EB0089B766 /* field_value_test.cc */, ABF6506B201131F8005F2C74 /* timestamp_test.cc */, @@ -1240,6 +1243,7 @@ DE51B1F81F0D491F0013853F /* FSTWatchChange+Testing.m in Sources */, DE51B1EB1F0D490D0013853F /* FSTWriteGroupTests.mm in Sources */, 54C2294F1FECABAE007D065B /* log_test.cc in Sources */, + B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */, DE51B2011F0D493E0013853F /* FSTHelpers.m in Sources */, AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */, DE51B1F61F0D491B0013853F /* FSTSerializerBetaTests.m in Sources */, diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index ac01b5eb7bf..63ed813b2e4 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -19,6 +19,7 @@ cc_test( field_path_test.cc field_value_test.cc timestamp_test.cc + resource_path_test.cc DEPENDS firebase_firestore_model ) diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 008ef8a1c21..a991a61a056 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -266,6 +266,7 @@ TEST(FieldPath, CanonicalStringEscaping) { // empty path, shared key path // // port comments +// variable names, include order // // PUBLIC CLASS diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc new file mode 100644 index 00000000000..8ae26fd4d8f --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -0,0 +1,81 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" + +#include +#include +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +TEST(ResourcePath, Constructor) { + const ResourcePath empty_path; + EXPECT_TRUE(empty_path.empty()); + EXPECT_EQ(0, empty_path.size()); + EXPECT_TRUE(empty_path.begin() == empty_path.end()); + + const ResourcePath path_from_list{{"rooms", "Eros", "messages"}}; + EXPECT_FALSE(path_from_list.empty()); + EXPECT_EQ(3, path_from_list.size()); + EXPECT_TRUE(path_from_list.begin() + 3 == path_from_list.end()); + + std::vector segments{"rooms", "Eros", "messages"}; + const ResourcePath path_from_segments{segments.begin(), segments.end()}; + EXPECT_FALSE(path_from_segments.empty()); + EXPECT_EQ(3, path_from_segments.size()); + EXPECT_TRUE(path_from_segments.begin() + 3 == path_from_segments.end()); + + ResourcePath copied = path_from_list; + EXPECT_EQ(path_from_list, copied); + const ResourcePath moved = std::move(copied); + // Because ResourcePath is immutable, move constructor performs a copy. + EXPECT_EQ(copied, moved); +} + +TEST(ResourcePath, Parsing) { + const auto expect_round_trip = [](const std::string& str, + const size_t expected_segments) { + const auto path = ResourcePath::Parse(str); + EXPECT_EQ(str, path.CanonicalString()); + EXPECT_EQ(expected_segments, path.size()); + }; + + expect_round_trip("", 0); + expect_round_trip("foo", 1); + expect_round_trip("foo/bar", 2); + expect_round_trip("foo/bar/baz", 3); + expect_round_trip(R"(foo/__..`..\`/baz)", 3); + + EXPECT_EQ("foo", ResourcePath::Parse("/foo/").CanonicalString()); +} + +TEST(ResourcePath, ParseFailures) { + const auto expect_fail = [](const absl::string_view str) { + ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse(str), ""); + }; + + expect_fail("//"); + expect_fail("foo//bar"); +} + +} // namespace model +} // namespace firestore +} // namespace firebase From 7761737498b0c4adc9060ada9e961e64579034a9 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 15:29:42 -0500 Subject: [PATCH 27/48] remove helper functions with asserts in tests --- .../firestore/model/field_path_test.cc | 26 ++++++-------- .../firestore/model/resource_path_test.cc | 36 ++++++++++--------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index a991a61a056..7b078d22da8 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -221,21 +221,17 @@ TEST(FieldPath, ParseEmbeddedNull) { } TEST(FieldPath, ParseFailures) { - const auto expect_fail = [](const absl::string_view str) { - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(str), ""); - }; - - expect_fail(""); - expect_fail("."); - expect_fail(".."); - expect_fail("foo."); - expect_fail(".bar"); - expect_fail("foo..bar"); - expect_fail(R"(foo\)"); - expect_fail(R"(foo.\)"); - expect_fail("foo`"); - expect_fail("foo```"); - expect_fail("`foo"); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(""), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("."), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(".."), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo."), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(".bar"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo..bar"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(R"(foo\)"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(R"(foo.\)"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo`"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo```"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("`foo"), ""); } TEST(FieldPath, CanonicalStringOfSubstring) { diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 8ae26fd4d8f..93dbc41eb9d 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -51,29 +51,31 @@ TEST(ResourcePath, Constructor) { } TEST(ResourcePath, Parsing) { - const auto expect_round_trip = [](const std::string& str, - const size_t expected_segments) { - const auto path = ResourcePath::Parse(str); - EXPECT_EQ(str, path.CanonicalString()); - EXPECT_EQ(expected_segments, path.size()); + const auto parse = [](const std::pair expected) { + const auto path = ResourcePath::Parse(expected.first); + return std::make_pair(path.CanonicalString(), path.size()); + }; + const auto make_expected = [](const std::string& str, const size_t size) { + return std::make_pair(str, size); }; - expect_round_trip("", 0); - expect_round_trip("foo", 1); - expect_round_trip("foo/bar", 2); - expect_round_trip("foo/bar/baz", 3); - expect_round_trip(R"(foo/__..`..\`/baz)", 3); + auto expected = make_expected("", 0); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected("foo", 1); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected("foo/bar", 2); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected("foo/bar/baz", 3); + EXPECT_EQ(expected, parse(expected)); + expected = make_expected(R"(foo/__..`..\`/baz)", 3); + EXPECT_EQ(expected, parse(expected)); - EXPECT_EQ("foo", ResourcePath::Parse("/foo/").CanonicalString()); + EXPECT_EQ(ResourcePath::Parse("/foo/").CanonicalString(), "foo"); } TEST(ResourcePath, ParseFailures) { - const auto expect_fail = [](const absl::string_view str) { - ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse(str), ""); - }; - - expect_fail("//"); - expect_fail("foo//bar"); + ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse("//"), ""); + ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse("foo//bar"), ""); } } // namespace model From 6404f06c1774eb0de1d966f1df21cdd33e4ffb3c Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 15:31:27 -0500 Subject: [PATCH 28/48] a weirder string to test parsing, update todo --- .../core/test/firebase/firestore/model/field_path_test.cc | 3 --- .../core/test/firebase/firestore/model/resource_path_test.cc | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 7b078d22da8..c4d76ad7a9d 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -260,11 +260,8 @@ TEST(FieldPath, CanonicalStringEscaping) { // TODO: // empty path, shared key path -// // port comments // variable names, include order -// -// PUBLIC CLASS } // namespace model } // namespace firestore diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 93dbc41eb9d..2aa4a1aaaff 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -67,7 +67,7 @@ TEST(ResourcePath, Parsing) { EXPECT_EQ(expected, parse(expected)); expected = make_expected("foo/bar/baz", 3); EXPECT_EQ(expected, parse(expected)); - expected = make_expected(R"(foo/__..`..\`/baz)", 3); + expected = make_expected(R"(foo/__!?#@..`..\`/baz)", 3); EXPECT_EQ(expected, parse(expected)); EXPECT_EQ(ResourcePath::Parse("/foo/").CanonicalString(), "foo"); From ef584ec6d716a8fff8a2e187c4a114b2e607cee6 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 15:36:36 -0500 Subject: [PATCH 29/48] all comparison operators + tests --- .../src/firebase/firestore/model/base_path.h | 6 ++++++ .../firebase/firestore/model/field_path.cc | 13 ------------ .../src/firebase/firestore/model/field_path.h | 14 +++++++------ .../firebase/firestore/model/resource_path.h | 19 +++++++++++++++++ .../firestore/model/resource_path_test.cc | 21 +++++++++++++++++++ 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index c1c2089dc1b..fd68d78acaa 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -116,6 +116,12 @@ class BasePath { bool operator>(const BasePath& rhs) const { return segments_ > rhs.segments_; } + bool operator<=(const BasePath& rhs) const { + return segments_ <= rhs.segments_; + } + bool operator>=(const BasePath& rhs) const { + return segments_ >= rhs.segments_; + } // std::hash // to_string diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index bb7f79f1be2..cf659473846 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -140,19 +140,6 @@ std::string FieldPath::CanonicalString() const { }); } -// OBC: do we really need emptypath? -// OBC: do we really need *shared* keypath? - -// bool operator<(const FieldPath& lhs, const FieldPath& rhs) { -// return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), -// rhs.end()); -// } - -// bool operator==(const FieldPath& lhs, const FieldPath& rhs) { -// return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), -// rhs.end()); -// } - } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 3d3fcf0cd3b..99f4a128be0 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -53,18 +53,20 @@ class FieldPath : public impl::BasePath { bool operator>(const FieldPath& rhs) const { return BasePath::operator>(rhs); } + bool operator<=(const FieldPath& rhs) const { + return BasePath::operator<=(rhs); + } + bool operator>=(const FieldPath& rhs) const { + return BasePath::operator>=(rhs); + } - // OBC: do we really need emptypath? - // OBC: do we really need *shared* keypath? private: - FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} {} + FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { + } friend class BasePath; }; -// bool operator<(const FieldPath& lhs, const FieldPath& rhs); -// bool operator==(const FieldPath& lhs, const FieldPath& rhs); - } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index 4c731860948..83fe952fd11 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -40,6 +40,25 @@ class ResourcePath : public impl::BasePath { std::string CanonicalString() const; + bool operator==(const ResourcePath& rhs) const { + return BasePath::operator==(rhs); + } + bool operator!=(const ResourcePath& rhs) const { + return BasePath::operator!=(rhs); + } + bool operator<(const ResourcePath& rhs) const { + return BasePath::operator<(rhs); + } + bool operator>(const ResourcePath& rhs) const { + return BasePath::operator>(rhs); + } + bool operator<=(const ResourcePath& rhs) const { + return BasePath::operator<=(rhs); + } + bool operator>=(const ResourcePath& rhs) const { + return BasePath::operator>=(rhs); + } + private: ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { } diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 2aa4a1aaaff..9ef8b40d90a 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -50,6 +50,27 @@ TEST(ResourcePath, Constructor) { EXPECT_EQ(copied, moved); } +TEST(ResourcePath, Comparison) { + const ResourcePath abc{"a", "b", "c"}; + const ResourcePath abc2{"a", "b", "c"}; + const ResourcePath xyz{"x", "y", "z"}; + EXPECT_EQ(abc, abc2); + EXPECT_NE(abc, xyz); + + const ResourcePath empty; + const ResourcePath a{"a"}; + const ResourcePath b{"b"}; + const ResourcePath ab{"a", "b"}; + + EXPECT_TRUE(empty < a); + EXPECT_TRUE(a < b); + EXPECT_TRUE(a < ab); + + EXPECT_TRUE(a > empty); + EXPECT_TRUE(b > a); + EXPECT_TRUE(ab > a); +} + TEST(ResourcePath, Parsing) { const auto parse = [](const std::pair expected) { const auto path = ResourcePath::Parse(expected.first); From 068e15ca94f447a0a298cc3bc07d01df2825478e Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 15:58:43 -0500 Subject: [PATCH 30/48] keyfieldpath --- .../core/src/firebase/firestore/model/field_path.cc | 11 +++++++++++ .../core/src/firebase/firestore/model/field_path.h | 2 ++ .../test/firebase/firestore/model/field_path_test.cc | 12 ++++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index cf659473846..ef9cb1069d3 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -32,6 +32,9 @@ namespace model { namespace { +// TODO(varconst): move to C++ equivalent of FSTDocumentKey.{h,cc} +const char* const kDocumentKeyPath = "__name__"; + bool IsValidIdentifier(const std::string& segment) { if (segment.empty()) { return false; @@ -133,6 +136,14 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { return FieldPath{std::move(segments)}; } +FieldPath FieldPath::KeyFieldPath() { + return FieldPath{kDocumentFieldKeyPath}; +} + +bool FieldPath::IsKeyFieldPath() const { + return size() == 1 && front() == kDocumentFieldKeyPath; +} + std::string FieldPath::CanonicalString() const { return absl::StrJoin(begin(), end(), ".", [](std::string* out, const std::string& segment) { diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 99f4a128be0..67881a3db2e 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -38,8 +38,10 @@ class FieldPath : public impl::BasePath { FieldPath(std::initializer_list list) : BasePath{list} { } static FieldPath ParseServerFormat(absl::string_view path); + static FieldPath KeyFieldPath(); std::string CanonicalString() const; + bool IsKeyFieldPath() const; bool operator==(const FieldPath& rhs) const { return BasePath::operator==(rhs); diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index c4d76ad7a9d..1a1be10f184 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -258,10 +258,14 @@ TEST(FieldPath, CanonicalStringEscaping) { EXPECT_EQ(FieldPath::ParseServerFormat("a_").CanonicalString(), "a_"); } -// TODO: -// empty path, shared key path -// port comments -// variable names, include order +TEST(FieldPath, CreateKeyFieldPath) { + const auto key_field_path = FieldPath::KeyFieldPath(); + EXPECT_EQ(key_field_path, FieldPath{key_field_path}); + EXPECT_EQ(key_field_path, + FieldPath::ParseServerFormat(key_field_path.CanonicalString())); + EXPECT_NE(key_field_path, FieldPath::ParseServerFormat( + key_field_path.CanonicalString().substr(1))); +} } // namespace model } // namespace firestore From 1a071a4f0e9d6ac682eff95201374f64f75493d7 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 16:01:17 -0500 Subject: [PATCH 31/48] fix wrong name --- Firestore/core/src/firebase/firestore/model/field_path.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index ef9cb1069d3..f39db413c17 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -137,11 +137,11 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { } FieldPath FieldPath::KeyFieldPath() { - return FieldPath{kDocumentFieldKeyPath}; + return FieldPath{kDocumentKeyPath}; } bool FieldPath::IsKeyFieldPath() const { - return size() == 1 && front() == kDocumentFieldKeyPath; + return size() == 1 && front() == kDocumentKeyPath; } std::string FieldPath::CanonicalString() const { From 6c14417d6c2142cd60bef78baca265fd8a8a1b52 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 16:38:00 -0500 Subject: [PATCH 32/48] comments --- .../src/firebase/firestore/model/base_path.h | 60 ++++++++++++++----- .../firebase/firestore/model/field_path.cc | 12 +++- .../src/firebase/firestore/model/field_path.h | 17 ++++++ .../firebase/firestore/model/resource_path.h | 12 ++++ .../firestore/model/field_path_test.cc | 1 + 5 files changed, 86 insertions(+), 16 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index fd68d78acaa..233debb4143 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -26,15 +26,26 @@ #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" -#include "absl/strings/str_join.h" -#include "absl/strings/str_replace.h" -#include "absl/strings/str_split.h" - namespace firebase { namespace firestore { namespace model { namespace impl { +/** + * BasePath represents a path sequence in the Firestore database. It is composed + * of an ordered sequence of string segments. + * + * BasePath is immutable. All mutating operations return new independent + * instances. + * + * ## Subclassing Notes + * + * BasePath is strictly meant as a base class for concrete implementations. It + * doesn't contain a single virtual method, can't be instantiated, and should + * never be used in any polymorphic way. BasePath is templated to allow static + * factory methods to return objects of the derived class (the expected + * inheritance involves CRTP: struct Derived : BasePath). + */ template class BasePath { protected: @@ -43,19 +54,21 @@ class BasePath { public: using const_iterator = SegmentsT::const_iterator; + /** Returns i-th segment of the path. */ const std::string& operator[](const size_t i) const { return at(i); } - const std::string& at(const size_t i) const { FIREBASE_ASSERT_MESSAGE(i < segments_.size(), "index %u out of range", i); return segments_[i]; } + /** Returns first segment of the path. */ const std::string& front() const { FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call front on empty path"); return at(0); } + /** Returns last segment of the path. */ const std::string& back() const { FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call back on empty path"); return at(size() - 1); @@ -64,7 +77,6 @@ class BasePath { size_t size() const { return segments_.size(); } - bool empty() const { return segments_.empty(); } @@ -76,30 +88,51 @@ class BasePath { return segments_.end(); } + /** + * Returns a new path which is the result of concatenating this path with an + * additional segment. + */ T Concat(const std::string& segment) const { auto concatenated = segments_; concatenated.push_back(segment); return T{std::move(concatenated)}; } + /** + * Returns a new path which is the result of concatenating this path with an + * another path. + */ T Concat(const T& path) const { auto concatenated = segments_; concatenated.insert(concatenated.end(), path.begin(), path.end()); return T{std::move(concatenated)}; } - T DropFirst(const size_t count = 1) const { - FIREBASE_ASSERT_MESSAGE(count <= size(), - "Cannot call DropFirst(%u) on path of length %u", - count, size()); - return T{segments_.begin() + count, segments_.end()}; + /** + * Returns a new path which is the result of dropping the first n segments of + * this path. + */ + T DropFirst(const size_t n = 1) const { + FIREBASE_ASSERT_MESSAGE(n <= size(), + "Cannot call DropFirst(%u) on path of length %u", n, + size()); + return T{begin() + n, end()}; } + /** + * Returns a new path which is the result of dropping the last segment of + * this path. + */ T DropLast() const { FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call DropLast() on empty path"); - return T{segments_.begin(), segments_.end() - 1}; + return T{begin(), end() - 1}; } + /** + * Returns true if this path is a prefix of the given path. + * + * Empty path is prefix of any path. Any path is prefix of itself. + */ bool IsPrefixOf(const T& rhs) const { return size() <= rhs.size() && std::equal(begin(), end(), rhs.begin()); } @@ -123,9 +156,6 @@ class BasePath { return segments_ >= rhs.segments_; } - // std::hash - // to_string - protected: BasePath() = default; template diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index f39db413c17..c6b2f8ece1b 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -35,6 +35,10 @@ namespace { // TODO(varconst): move to C++ equivalent of FSTDocumentKey.{h,cc} const char* const kDocumentKeyPath = "__name__"; +/** + * True if the string could be used as a segment in a field path without + * escaping. + */ bool IsValidIdentifier(const std::string& segment) { if (segment.empty()) { return false; @@ -73,6 +77,9 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { std::string segment; segment.reserve(path.size()); + // string_view doesn't have a c_str() method, because it might not be + // null-terminated. Assertions expect C strings, so construct std::string on + // the fly, so that c_str() might be called on it. const auto to_string = [](const absl::string_view view) { return std::string{view.data(), view.data() + view.size()}; }; @@ -83,7 +90,7 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { "'.', end with '.', or contain '..'", to_string(path).c_str()); // Move operation will clear segment, but capacity will remain the same - // (not strictly speaking required by the standard, but true in practice). + // (not, strictly speaking, required by the standard, but true in practice). segments.push_back(std::move(segment)); }; @@ -92,6 +99,9 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { // Whether to treat '\' literally or as an escape character. bool escapedCharacter = false; for (const char c : path) { + // std::string (and string_view) may contain embedded nulls. For full + // compatibility with Objective C behavior, finish upon encountering the + // first terminating null. if (c == '\0') { break; } diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 67881a3db2e..3696ccfee08 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -29,18 +29,33 @@ namespace firebase { namespace firestore { namespace model { +/** + * A dot-separated path for navigating sub-objects within a document. + * + * Immutable; all instances are fully independent. + */ class FieldPath : public impl::BasePath { public: FieldPath() = default; + /** Constructs the path from segments. */ template FieldPath(const IterT begin, const IterT end) : BasePath{begin, end} { } FieldPath(std::initializer_list list) : BasePath{list} { } + + /** + * Creates and returns a new path from the server formatted field-path string, + * where path segments are separated by a dot "." and optionally encoded using + * backticks. + */ static FieldPath ParseServerFormat(absl::string_view path); + /** Returns a field path that represents a document key. */ static FieldPath KeyFieldPath(); + /** Returns a standardized string representation of this path. */ std::string CanonicalString() const; + /** True if this FieldPath represents a document key. */ bool IsKeyFieldPath() const; bool operator==(const FieldPath& rhs) const { @@ -66,6 +81,8 @@ class FieldPath : public impl::BasePath { FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { } + // So that methods of base can construct ResourcePath using the private + // constructor. friend class BasePath; }; diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index 83fe952fd11..3b3759ad547 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -28,16 +28,26 @@ namespace firebase { namespace firestore { namespace model { +/** + * A slash-separated path for navigating resources (documents and collections) + * within Firestore. Immutable; all instances are fully independent. + */ class ResourcePath : public impl::BasePath { public: ResourcePath() = default; + /** Constructs the path from segments. */ template ResourcePath(const IterT begin, const IterT end) : BasePath{begin, end} { } ResourcePath(std::initializer_list list) : BasePath{list} { } + /** + * Creates and returns a new path from the given resource-path string, where + * the path segments are separated by a slash "/". + */ static ResourcePath Parse(absl::string_view path); + /** Returns a standardized string representation of this path. */ std::string CanonicalString() const; bool operator==(const ResourcePath& rhs) const { @@ -63,6 +73,8 @@ class ResourcePath : public impl::BasePath { ResourcePath(SegmentsT&& segments) : BasePath{std::move(segments)} { } + // So that methods of base can construct ResourcePath using the private + // constructor. friend class BasePath; }; diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 1a1be10f184..c759cfde0b4 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -104,6 +104,7 @@ TEST(FieldPath, Concatenation) { EXPECT_EQ(ab, path.Concat("rooms").Concat("Eros")); EXPECT_EQ(abc, path.Concat("rooms").Concat("Eros").Concat("messages")); EXPECT_EQ(abc, path.Concat(FieldPath{"rooms", "Eros", "messages"})); + EXPECT_EQ(abc, path.Concat({"rooms", "Eros", "messages"})); const FieldPath bcd{"Eros", "messages", "this_week"}; EXPECT_EQ(bcd, abc.DropFirst().Concat("this_week")); From 05053702de357b8d47b593c66bc5d9280c1d91ca Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 16:42:44 -0500 Subject: [PATCH 33/48] naming --- .../src/firebase/firestore/model/field_path.cc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index c6b2f8ece1b..66e579c85d0 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -95,9 +95,9 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { }; // Inside backticks, dots are treated literally. - bool insideBackticks = false; + bool inside_backticks = false; // Whether to treat '\' literally or as an escape character. - bool escapedCharacter = false; + bool escaped_character = false; for (const char c : path) { // std::string (and string_view) may contain embedded nulls. For full // compatibility with Objective C behavior, finish upon encountering the @@ -105,15 +105,15 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { if (c == '\0') { break; } - if (escapedCharacter) { - escapedCharacter = false; + if (escaped_character) { + escaped_character = false; segment += c; continue; } switch (c) { case '.': - if (!insideBackticks) { + if (!inside_backticks) { finish_segment(); } else { segment += c; @@ -121,11 +121,11 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { break; case '`': - insideBackticks = !insideBackticks; + inside_backticks = !inside_backticks; break; case '\\': - escapedCharacter = true; + escaped_character = true; break; default: @@ -135,11 +135,11 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { } finish_segment(); - FIREBASE_ASSERT_MESSAGE(!insideBackticks, "Unterminated ` in path %s", + FIREBASE_ASSERT_MESSAGE(!inside_backticks, "Unterminated ` in path %s", to_string(path).c_str()); // TODO(b/37244157): Make this a user-facing exception once we // finalize field escaping. - FIREBASE_ASSERT_MESSAGE(!escapedCharacter, + FIREBASE_ASSERT_MESSAGE(!escaped_character, "Trailing escape characters not allowed in %s", to_string(path).c_str()); From bb39ef9306377b0d31057de4a38731a1598754d4 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 16:48:16 -0500 Subject: [PATCH 34/48] style guide order of includes --- Firestore/core/src/firebase/firestore/model/base_path.h | 5 +++++ Firestore/core/src/firebase/firestore/model/field_path.cc | 3 +-- Firestore/core/src/firebase/firestore/model/field_path.h | 3 +-- Firestore/core/src/firebase/firestore/model/resource_path.cc | 3 +-- Firestore/core/src/firebase/firestore/model/resource_path.h | 3 +-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 233debb4143..564b9733796 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -97,6 +97,11 @@ class BasePath { concatenated.push_back(segment); return T{std::move(concatenated)}; } + T Concat(std::string&& segment) const { + auto concatenated = segments_; + concatenated.push_back(std::move(segment)); + return T{std::move(concatenated)}; + } /** * Returns a new path which is the result of concatenating this path with an diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 66e579c85d0..f43ce5d4184 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -16,12 +16,11 @@ #include "Firestore/core/src/firebase/firestore/model/field_path.h" -#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" - #include #include #include +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include "absl/strings/str_join.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 3696ccfee08..d9fe577816a 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -21,9 +21,8 @@ #include #include -#include "Firestore/core/src/firebase/firestore/model/base_path.h" - #include "absl/strings/string_view.h" +#include "Firestore/core/src/firebase/firestore/model/base_path.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index d85518bfb93..36218e98dd6 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -16,11 +16,10 @@ #include "Firestore/core/src/firebase/firestore/model/resource_path.h" -#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" - #include #include +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" #include "absl/strings/str_join.h" #include "absl/strings/str_split.h" diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index 3b3759ad547..d5a19cea491 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -20,9 +20,8 @@ #include #include -#include "Firestore/core/src/firebase/firestore/model/base_path.h" - #include "absl/strings/string_view.h" +#include "Firestore/core/src/firebase/firestore/model/base_path.h" namespace firebase { namespace firestore { From 355f5069b5b12abe676d16f5381c799235d98101 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 2 Feb 2018 16:48:34 -0500 Subject: [PATCH 35/48] remove reminder --- Firestore/core/test/firebase/firestore/model/field_path_test.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index c759cfde0b4..37056debb4f 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -202,7 +202,6 @@ TEST(FieldPath, Parsing) { expected = make_expected(R"(foo.`\``.bar)", 3); EXPECT_EQ(expected, parse(expected)); - // OBC this is probably a bug (behavior equivalent to Obj-C) const auto path_with_dot = FieldPath::ParseServerFormat(R"(foo\.bar)"); EXPECT_EQ(path_with_dot.CanonicalString(), "`foo.bar`"); EXPECT_EQ(path_with_dot.size(), 1); From 62e45a135caa3a9bb2ae15cb4345d129acae29bd Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Mon, 5 Feb 2018 15:22:30 -0500 Subject: [PATCH 36/48] Review feedback. TODO: - Empty/KeyPath; - 'a'-'z' --- .../src/firebase/firestore/model/base_path.h | 66 +++++------ .../firebase/firestore/model/field_path.cc | 79 +++++++------ .../src/firebase/firestore/model/field_path.h | 2 +- .../firestore/model/field_path_test.cc | 111 +++++++++--------- .../firestore/model/resource_path_test.cc | 5 +- 5 files changed, 131 insertions(+), 132 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h index 564b9733796..e7d8a53e8d9 100644 --- a/Firestore/core/src/firebase/firestore/model/base_path.h +++ b/Firestore/core/src/firebase/firestore/model/base_path.h @@ -35,8 +35,8 @@ namespace impl { * BasePath represents a path sequence in the Firestore database. It is composed * of an ordered sequence of string segments. * - * BasePath is immutable. All mutating operations return new independent - * instances. + * BasePath is reassignable and movable. Apart from those, all other mutating + * operations return new independent instances. * * ## Subclassing Notes * @@ -44,7 +44,7 @@ namespace impl { * doesn't contain a single virtual method, can't be instantiated, and should * never be used in any polymorphic way. BasePath is templated to allow static * factory methods to return objects of the derived class (the expected - * inheritance involves CRTP: struct Derived : BasePath). + * inheritance would use CRTP: struct Derived : BasePath). */ template class BasePath { @@ -56,22 +56,19 @@ class BasePath { /** Returns i-th segment of the path. */ const std::string& operator[](const size_t i) const { - return at(i); - } - const std::string& at(const size_t i) const { FIREBASE_ASSERT_MESSAGE(i < segments_.size(), "index %u out of range", i); return segments_[i]; } - /** Returns first segment of the path. */ - const std::string& front() const { - FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call front on empty path"); - return at(0); + /** Returns the first segment of the path. */ + const std::string& first_segment() const { + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call first_segment on empty path"); + return segments_[0]; } - /** Returns last segment of the path. */ - const std::string& back() const { - FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call back on empty path"); - return at(size() - 1); + /** Returns the last segment of the path. */ + const std::string& last_segment() const { + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call last_segment on empty path"); + return segments_[size() - 1]; } size_t size() const { @@ -92,51 +89,51 @@ class BasePath { * Returns a new path which is the result of concatenating this path with an * additional segment. */ - T Concat(const std::string& segment) const { - auto concatenated = segments_; - concatenated.push_back(segment); - return T{std::move(concatenated)}; + T Append(const std::string& segment) const { + auto appended = segments_; + appended.push_back(segment); + return T{std::move(appended)}; } - T Concat(std::string&& segment) const { - auto concatenated = segments_; - concatenated.push_back(std::move(segment)); - return T{std::move(concatenated)}; + T Append(std::string&& segment) const { + auto appended = segments_; + appended.push_back(std::move(segment)); + return T{std::move(appended)}; } /** * Returns a new path which is the result of concatenating this path with an * another path. */ - T Concat(const T& path) const { - auto concatenated = segments_; - concatenated.insert(concatenated.end(), path.begin(), path.end()); - return T{std::move(concatenated)}; + T Append(const T& path) const { + auto appended = segments_; + appended.insert(appended.end(), path.begin(), path.end()); + return T{std::move(appended)}; } /** - * Returns a new path which is the result of dropping the first n segments of + * Returns a new path which is the result of omitting the first n segments of * this path. */ - T DropFirst(const size_t n = 1) const { + T PopFirst(const size_t n = 1) const { FIREBASE_ASSERT_MESSAGE(n <= size(), - "Cannot call DropFirst(%u) on path of length %u", n, + "Cannot call PopFirst(%u) on path of length %u", n, size()); return T{begin() + n, end()}; } /** - * Returns a new path which is the result of dropping the last segment of + * Returns a new path which is the result of omitting the last segment of * this path. */ - T DropLast() const { - FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call DropLast() on empty path"); + T PopLast() const { + FIREBASE_ASSERT_MESSAGE(!empty(), "Cannot call PopLast() on empty path"); return T{begin(), end() - 1}; } /** * Returns true if this path is a prefix of the given path. * - * Empty path is prefix of any path. Any path is prefix of itself. + * Empty path is a prefix of any path. Any path is a prefix of itself. */ bool IsPrefixOf(const T& rhs) const { return size() <= rhs.size() && std::equal(begin(), end(), rhs.begin()); @@ -170,10 +167,9 @@ class BasePath { } BasePath(SegmentsT&& segments) : segments_{std::move(segments)} { } - ~BasePath() = default; private: - const SegmentsT segments_; + SegmentsT segments_; }; } // namespace impl diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index f43ce5d4184..61ce7d32bca 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -17,7 +17,6 @@ #include "Firestore/core/src/firebase/firestore/model/field_path.h" #include -#include #include #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" @@ -36,37 +35,36 @@ const char* const kDocumentKeyPath = "__name__"; /** * True if the string could be used as a segment in a field path without - * escaping. + * escaping. Valid identifies follow the regex [a-zA-Z_][a-zA-Z0-9_]* */ bool IsValidIdentifier(const std::string& segment) { if (segment.empty()) { return false; } - if (segment.front() != '_' && !std::isalpha(segment.front())) { + + // Note: strictly speaking, only digits are guaranteed by the Standard to + // be a contiguous range, while alphabetic characters may have gaps. Ignoring + // this peculiarity, because it doesn't affect the platforms that Firestore + // supports. + const unsigned char first = segment.front(); + if (first != '_' && + !('a' <= first && first <= 'z' || 'A' <= first && first <= 'Z')) { return false; } - if (std::any_of(segment.begin(), segment.end(), [](const unsigned char c) { - return c != '_' && !std::isalnum(c); - })) { - return false; + for (int i = 1; i != segment.size(); ++i) { + const unsigned char c = segment[i]; + if (c != '_' && !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || + '0' <= c && c <= '9')) { + return false; + } } return true; } -std::string EscapedSegment(const std::string& segment) { - auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); - const bool needs_escaping = !IsValidIdentifier(escaped); - if (needs_escaping) { - escaped.insert(escaped.begin(), '`'); - escaped.push_back('`'); - } - return escaped; -} - } // namespace -FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { +FieldPath FieldPath::FromServerFormat(const absl::string_view path) { // TODO(b/37244157): Once we move to v1beta1, we should make this more // strict. Right now, it allows non-identifier path components, even if they // aren't escaped. Technically, this will mangle paths with backticks in @@ -95,20 +93,15 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { // Inside backticks, dots are treated literally. bool inside_backticks = false; - // Whether to treat '\' literally or as an escape character. - bool escaped_character = false; - for (const char c : path) { + int i = 0; + while (i < path.size()) { + const char c = path[i]; // std::string (and string_view) may contain embedded nulls. For full // compatibility with Objective C behavior, finish upon encountering the // first terminating null. if (c == '\0') { break; } - if (escaped_character) { - escaped_character = false; - segment += c; - continue; - } switch (c) { case '.': @@ -124,23 +117,25 @@ FieldPath FieldPath::ParseServerFormat(const absl::string_view path) { break; case '\\': - escaped_character = true; + // TODO(b/37244157): Make this a user-facing exception once we + // finalize field escaping. + FIREBASE_ASSERT_MESSAGE(i + 1 != path.size(), + "Trailing escape characters not allowed in %s", + to_string(path).c_str()); + ++i; + segment += path[i]; break; default: segment += c; break; } + ++i; } finish_segment(); FIREBASE_ASSERT_MESSAGE(!inside_backticks, "Unterminated ` in path %s", to_string(path).c_str()); - // TODO(b/37244157): Make this a user-facing exception once we - // finalize field escaping. - FIREBASE_ASSERT_MESSAGE(!escaped_character, - "Trailing escape characters not allowed in %s", - to_string(path).c_str()); return FieldPath{std::move(segments)}; } @@ -150,14 +145,24 @@ FieldPath FieldPath::KeyFieldPath() { } bool FieldPath::IsKeyFieldPath() const { - return size() == 1 && front() == kDocumentKeyPath; + return size() == 1 && first_segment() == kDocumentKeyPath; } std::string FieldPath::CanonicalString() const { - return absl::StrJoin(begin(), end(), ".", - [](std::string* out, const std::string& segment) { - out->append(EscapedSegment(segment)); - }); + const auto escaped_segment = [](const std::string& segment) { + auto escaped = absl::StrReplaceAll(segment, {{"\\", "\\\\"}, {"`", "\\`"}}); + const bool needs_escaping = !IsValidIdentifier(escaped); + if (needs_escaping) { + escaped.insert(escaped.begin(), '`'); + escaped.push_back('`'); + } + return escaped; + }; + return absl::StrJoin( + begin(), end(), ".", + [escaped_segment](std::string* out, const std::string& segment) { + out->append(escaped_segment(segment)); + }); } } // namespace model diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index d9fe577816a..81a8dd8a8fd 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -48,7 +48,7 @@ class FieldPath : public impl::BasePath { * where path segments are separated by a dot "." and optionally encoded using * backticks. */ - static FieldPath ParseServerFormat(absl::string_view path); + static FieldPath FromServerFormat(absl::string_view path); /** Returns a field path that represents a document key. */ static FieldPath KeyFieldPath(); diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 37056debb4f..3f591c8caa2 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -46,26 +46,24 @@ TEST(FieldPath, Constructors) { FieldPath copied = path_from_list; EXPECT_EQ(path_from_list, copied); const FieldPath moved = std::move(copied); - // Because FieldPath is immutable, move constructor performs a copy. - EXPECT_EQ(copied, moved); + EXPECT_EQ(path_from_list, moved); + EXPECT_NE(copied, moved); + EXPECT_EQ(empty_path, copied); } TEST(FieldPath, Indexing) { const FieldPath path{"rooms", "Eros", "messages"}; - EXPECT_EQ(path.front(), "rooms"); + EXPECT_EQ(path.first_segment(), "rooms"); EXPECT_EQ(path[0], "rooms"); - EXPECT_EQ(path.at(0), "rooms"); EXPECT_EQ(path[1], "Eros"); - EXPECT_EQ(path.at(1), "Eros"); EXPECT_EQ(path[2], "messages"); - EXPECT_EQ(path.at(2), "messages"); - EXPECT_EQ(path.back(), "messages"); + EXPECT_EQ(path.last_segment(), "messages"); } -TEST(FieldPath, DropFirst) { +TEST(FieldPath, PopFirst) { const FieldPath abc{"rooms", "Eros", "messages"}; const FieldPath bc{"Eros", "messages"}; const FieldPath c{"messages"}; @@ -76,22 +74,22 @@ TEST(FieldPath, DropFirst) { EXPECT_NE(c, bc); EXPECT_NE(bc, abc); - EXPECT_EQ(bc, abc.DropFirst()); - EXPECT_EQ(c, abc.DropFirst(2)); - EXPECT_EQ(empty, abc.DropFirst(3)); + EXPECT_EQ(bc, abc.PopFirst()); + EXPECT_EQ(c, abc.PopFirst(2)); + EXPECT_EQ(empty, abc.PopFirst(3)); EXPECT_EQ(abc_dupl, abc); } -TEST(FieldPath, DropLast) { +TEST(FieldPath, PopLast) { const FieldPath abc{"rooms", "Eros", "messages"}; const FieldPath ab{"rooms", "Eros"}; const FieldPath a{"rooms"}; const FieldPath empty; const FieldPath abc_dupl{"rooms", "Eros", "messages"}; - EXPECT_EQ(ab, abc.DropLast()); - EXPECT_EQ(a, abc.DropLast().DropLast()); - EXPECT_EQ(empty, abc.DropLast().DropLast().DropLast()); + EXPECT_EQ(ab, abc.PopLast()); + EXPECT_EQ(a, abc.PopLast().PopLast()); + EXPECT_EQ(empty, abc.PopLast().PopLast().PopLast()); } TEST(FieldPath, Concatenation) { @@ -100,14 +98,14 @@ TEST(FieldPath, Concatenation) { const FieldPath ab{"rooms", "Eros"}; const FieldPath abc{"rooms", "Eros", "messages"}; - EXPECT_EQ(a, path.Concat("rooms")); - EXPECT_EQ(ab, path.Concat("rooms").Concat("Eros")); - EXPECT_EQ(abc, path.Concat("rooms").Concat("Eros").Concat("messages")); - EXPECT_EQ(abc, path.Concat(FieldPath{"rooms", "Eros", "messages"})); - EXPECT_EQ(abc, path.Concat({"rooms", "Eros", "messages"})); + EXPECT_EQ(a, path.Append("rooms")); + EXPECT_EQ(ab, path.Append("rooms").Append("Eros")); + EXPECT_EQ(abc, path.Append("rooms").Append("Eros").Append("messages")); + EXPECT_EQ(abc, path.Append(FieldPath{"rooms", "Eros", "messages"})); + EXPECT_EQ(abc, path.Append({"rooms", "Eros", "messages"})); const FieldPath bcd{"Eros", "messages", "this_week"}; - EXPECT_EQ(bcd, abc.DropFirst().Concat("this_week")); + EXPECT_EQ(bcd, abc.PopFirst().Append("this_week")); } TEST(FieldPath, Comparison) { @@ -170,19 +168,18 @@ TEST(FieldPath, IsPrefixOf) { TEST(FieldPath, AccessFailures) { const FieldPath path; - ASSERT_DEATH_IF_SUPPORTED(path.front(), ""); - ASSERT_DEATH_IF_SUPPORTED(path.back(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.first_segment(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.last_segment(), ""); ASSERT_DEATH_IF_SUPPORTED(path[0], ""); ASSERT_DEATH_IF_SUPPORTED(path[1], ""); - ASSERT_DEATH_IF_SUPPORTED(path.at(0), ""); - ASSERT_DEATH_IF_SUPPORTED(path.DropFirst(), ""); - ASSERT_DEATH_IF_SUPPORTED(path.DropFirst(2), ""); - ASSERT_DEATH_IF_SUPPORTED(path.DropLast(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.PopFirst(), ""); + ASSERT_DEATH_IF_SUPPORTED(path.PopFirst(2), ""); + ASSERT_DEATH_IF_SUPPORTED(path.PopLast(), ""); } TEST(FieldPath, Parsing) { const auto parse = [](const std::pair expected) { - const auto path = FieldPath::ParseServerFormat(expected.first); + const auto path = FieldPath::FromServerFormat(expected.first); return std::make_pair(path.CanonicalString(), path.size()); }; const auto make_expected = [](const std::string& str, const size_t size) { @@ -202,7 +199,7 @@ TEST(FieldPath, Parsing) { expected = make_expected(R"(foo.`\``.bar)", 3); EXPECT_EQ(expected, parse(expected)); - const auto path_with_dot = FieldPath::ParseServerFormat(R"(foo\.bar)"); + const auto path_with_dot = FieldPath::FromServerFormat(R"(foo\.bar)"); EXPECT_EQ(path_with_dot.CanonicalString(), "`foo.bar`"); EXPECT_EQ(path_with_dot.size(), 1); } @@ -215,55 +212,55 @@ TEST(FieldPath, ParseEmbeddedNull) { str += '\0'; str += ".bar"; - const auto path = FieldPath::ParseServerFormat(str); + const auto path = FieldPath::FromServerFormat(str); EXPECT_EQ(path.size(), 1); EXPECT_EQ(path.CanonicalString(), "foo"); } TEST(FieldPath, ParseFailures) { - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(""), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("."), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(".."), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo."), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(".bar"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo..bar"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(R"(foo\)"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat(R"(foo.\)"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo`"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("foo```"), ""); - ASSERT_DEATH_IF_SUPPORTED(FieldPath::ParseServerFormat("`foo"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(""), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("."), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(".."), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo."), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(".bar"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo..bar"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(R"(foo\)"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat(R"(foo.\)"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo`"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("foo```"), ""); + ASSERT_DEATH_IF_SUPPORTED(FieldPath::FromServerFormat("`foo"), ""); } TEST(FieldPath, CanonicalStringOfSubstring) { - const auto path = FieldPath::ParseServerFormat("foo.bar.baz"); + const auto path = FieldPath::FromServerFormat("foo.bar.baz"); EXPECT_EQ(path.CanonicalString(), "foo.bar.baz"); - EXPECT_EQ(path.DropFirst().CanonicalString(), "bar.baz"); - EXPECT_EQ(path.DropLast().CanonicalString(), "foo.bar"); - EXPECT_EQ(path.DropFirst().DropLast().CanonicalString(), "bar"); - EXPECT_EQ(path.DropFirst().DropLast().CanonicalString(), "bar"); - EXPECT_EQ(path.DropLast().DropFirst().DropLast().CanonicalString(), ""); + EXPECT_EQ(path.PopFirst().CanonicalString(), "bar.baz"); + EXPECT_EQ(path.PopLast().CanonicalString(), "foo.bar"); + EXPECT_EQ(path.PopFirst().PopLast().CanonicalString(), "bar"); + EXPECT_EQ(path.PopFirst().PopLast().CanonicalString(), "bar"); + EXPECT_EQ(path.PopLast().PopFirst().PopLast().CanonicalString(), ""); } TEST(FieldPath, CanonicalStringEscaping) { // Should be escaped - EXPECT_EQ(FieldPath::ParseServerFormat("1").CanonicalString(), "`1`"); - EXPECT_EQ(FieldPath::ParseServerFormat("1ab").CanonicalString(), "`1ab`"); - EXPECT_EQ(FieldPath::ParseServerFormat("ab!").CanonicalString(), "`ab!`"); - EXPECT_EQ(FieldPath::ParseServerFormat("/ab").CanonicalString(), "`/ab`"); - EXPECT_EQ(FieldPath::ParseServerFormat("a#b").CanonicalString(), "`a#b`"); + EXPECT_EQ(FieldPath::FromServerFormat("1").CanonicalString(), "`1`"); + EXPECT_EQ(FieldPath::FromServerFormat("1ab").CanonicalString(), "`1ab`"); + EXPECT_EQ(FieldPath::FromServerFormat("ab!").CanonicalString(), "`ab!`"); + EXPECT_EQ(FieldPath::FromServerFormat("/ab").CanonicalString(), "`/ab`"); + EXPECT_EQ(FieldPath::FromServerFormat("a#b").CanonicalString(), "`a#b`"); // Should not be escaped - EXPECT_EQ(FieldPath::ParseServerFormat("_ab").CanonicalString(), "_ab"); - EXPECT_EQ(FieldPath::ParseServerFormat("a1").CanonicalString(), "a1"); - EXPECT_EQ(FieldPath::ParseServerFormat("a_").CanonicalString(), "a_"); + EXPECT_EQ(FieldPath::FromServerFormat("_ab").CanonicalString(), "_ab"); + EXPECT_EQ(FieldPath::FromServerFormat("a1").CanonicalString(), "a1"); + EXPECT_EQ(FieldPath::FromServerFormat("a_").CanonicalString(), "a_"); } TEST(FieldPath, CreateKeyFieldPath) { const auto key_field_path = FieldPath::KeyFieldPath(); EXPECT_EQ(key_field_path, FieldPath{key_field_path}); EXPECT_EQ(key_field_path, - FieldPath::ParseServerFormat(key_field_path.CanonicalString())); - EXPECT_NE(key_field_path, FieldPath::ParseServerFormat( + FieldPath::FromServerFormat(key_field_path.CanonicalString())); + EXPECT_NE(key_field_path, FieldPath::FromServerFormat( key_field_path.CanonicalString().substr(1))); } diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 9ef8b40d90a..317a1dbeea4 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -46,8 +46,9 @@ TEST(ResourcePath, Constructor) { ResourcePath copied = path_from_list; EXPECT_EQ(path_from_list, copied); const ResourcePath moved = std::move(copied); - // Because ResourcePath is immutable, move constructor performs a copy. - EXPECT_EQ(copied, moved); + EXPECT_EQ(path_from_list, moved); + EXPECT_NE(copied, moved); + EXPECT_EQ(empty_path, copied); } TEST(ResourcePath, Comparison) { From b93f2f43ef796c842c988fc18edb771767826232 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Mon, 5 Feb 2018 15:48:17 -0500 Subject: [PATCH 37/48] finish review comments --- .../src/firebase/firestore/model/field_path.cc | 18 ++++++++++++------ .../src/firebase/firestore/model/field_path.h | 6 ++++-- .../firestore/model/field_path_test.cc | 12 ++++++++++-- 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 61ce7d32bca..6c406001747 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -47,14 +47,14 @@ bool IsValidIdentifier(const std::string& segment) { // this peculiarity, because it doesn't affect the platforms that Firestore // supports. const unsigned char first = segment.front(); - if (first != '_' && - !('a' <= first && first <= 'z' || 'A' <= first && first <= 'Z')) { + if (first != '_' && (first < 'a' || first > 'z') && + (first < 'A' || first > 'Z')) { return false; } for (int i = 1; i != segment.size(); ++i) { const unsigned char c = segment[i]; - if (c != '_' && !('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || - '0' <= c && c <= '9')) { + if (c != '_' && (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && + (c < '0' || c > '9')) { return false; } } @@ -140,8 +140,14 @@ FieldPath FieldPath::FromServerFormat(const absl::string_view path) { return FieldPath{std::move(segments)}; } -FieldPath FieldPath::KeyFieldPath() { - return FieldPath{kDocumentKeyPath}; +const FieldPath& FieldPath::EmptyPath() { + static const FieldPath empty_path; + return empty_path; +} + +const FieldPath& FieldPath::KeyFieldPath() { + static const FieldPath key_field_path{kDocumentKeyPath}; + return key_field_path; } bool FieldPath::IsKeyFieldPath() const { diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 81a8dd8a8fd..33423d76d5a 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -49,8 +49,10 @@ class FieldPath : public impl::BasePath { * backticks. */ static FieldPath FromServerFormat(absl::string_view path); + /** Returns a field path that represents an empty path. */ + static const FieldPath& EmptyPath(); /** Returns a field path that represents a document key. */ - static FieldPath KeyFieldPath(); + static const FieldPath& KeyFieldPath(); /** Returns a standardized string representation of this path. */ std::string CanonicalString() const; @@ -80,7 +82,7 @@ class FieldPath : public impl::BasePath { FieldPath(SegmentsT&& segments) : BasePath{std::move(segments)} { } - // So that methods of base can construct ResourcePath using the private + // So that methods of base can construct FieldPath using the private // constructor. friend class BasePath; }; diff --git a/Firestore/core/test/firebase/firestore/model/field_path_test.cc b/Firestore/core/test/firebase/firestore/model/field_path_test.cc index 3f591c8caa2..7c7e0a32e3a 100644 --- a/Firestore/core/test/firebase/firestore/model/field_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/field_path_test.cc @@ -255,11 +255,19 @@ TEST(FieldPath, CanonicalStringEscaping) { EXPECT_EQ(FieldPath::FromServerFormat("a_").CanonicalString(), "a_"); } -TEST(FieldPath, CreateKeyFieldPath) { - const auto key_field_path = FieldPath::KeyFieldPath(); +TEST(FieldPath, EmptyPath) { + const auto& empty_path = FieldPath::EmptyPath(); + EXPECT_EQ(empty_path, FieldPath{empty_path}); + EXPECT_EQ(empty_path, FieldPath{}); + EXPECT_EQ(&empty_path, &FieldPath::EmptyPath()); +} + +TEST(FieldPath, KeyFieldPath) { + const auto& key_field_path = FieldPath::KeyFieldPath(); EXPECT_EQ(key_field_path, FieldPath{key_field_path}); EXPECT_EQ(key_field_path, FieldPath::FromServerFormat(key_field_path.CanonicalString())); + EXPECT_EQ(&key_field_path, &FieldPath::KeyFieldPath()); EXPECT_NE(key_field_path, FieldPath::FromServerFormat( key_field_path.CanonicalString().substr(1))); } From dfd73cb81bc8ebaf1ba1c0fced815183c5c7fa97 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Tue, 6 Feb 2018 15:03:41 -0500 Subject: [PATCH 38/48] Remove accidentally added files --- .../abseil-cpp/absl/numeric/int128.h.orig | 623 ------------------ .../absl/strings/CMakeLists.txt.orig | 249 ------- 2 files changed, 872 deletions(-) delete mode 100644 Firestore/third_party/abseil-cpp/absl/numeric/int128.h.orig delete mode 100644 Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt.orig diff --git a/Firestore/third_party/abseil-cpp/absl/numeric/int128.h.orig b/Firestore/third_party/abseil-cpp/absl/numeric/int128.h.orig deleted file mode 100644 index bbb76edb4ba..00000000000 --- a/Firestore/third_party/abseil-cpp/absl/numeric/int128.h.orig +++ /dev/null @@ -1,623 +0,0 @@ -// -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// ----------------------------------------------------------------------------- -// File: int128.h -// ----------------------------------------------------------------------------- -// -// This header file defines 128-bit integer types. Currently, this file defines -// `uint128`, an unsigned 128-bit integer; a signed 128-bit integer is -// forthcoming. - -#ifndef ABSL_NUMERIC_INT128_H_ -#define ABSL_NUMERIC_INT128_H_ - -#include -#include -#include -#include -#include -#include - -#include "absl/base/config.h" -#include "absl/base/macros.h" -#include "absl/base/port.h" - -namespace absl { - -// uint128 -// -// An unsigned 128-bit integer type. The API is meant to mimic an intrinsic type -// as closely as is practical, including exhibiting undefined behavior in -// analogous cases (e.g. division by zero). This type is intended to be a -// drop-in replacement once C++ supports an intrinsic `uint128_t` type; when -// that occurs, existing uses of `uint128` will continue to work using that new -// type. -// -// Note: code written with this type will continue to compile once `uint128_t` -// is introduced, provided the replacement helper functions -// `Uint128(Low|High)64()` and `MakeUint128()` are made. -// -// A `uint128` supports the following: -// -// * Implicit construction from integral types -// * Explicit conversion to integral types -// -// Additionally, if your compiler supports `__int128`, `uint128` is -// interoperable with that type. (Abseil checks for this compatibility through -// the `ABSL_HAVE_INTRINSIC_INT128` macro.) -// -// However, a `uint128` differs from intrinsic integral types in the following -// ways: -// -// * Errors on implicit conversions that do not preserve value (such as -// loss of precision when converting to float values). -// * Requires explicit construction from and conversion to floating point -// types. -// * Conversion to integral types requires an explicit static_cast() to -// mimic use of the `-Wnarrowing` compiler flag. -// -// Example: -// -// float y = absl::Uint128Max(); // Error. uint128 cannot be implicitly -// // converted to float. -// -// absl::uint128 v; -// absl::uint64_t i = v; // Error -// absl::uint64_t i = static_cast(v); // OK -// -class alignas(16) uint128 { - public: - uint128() = default; - - // Constructors from arithmetic types - constexpr uint128(int v); // NOLINT(runtime/explicit) - constexpr uint128(unsigned int v); // NOLINT(runtime/explicit) - constexpr uint128(long v); // NOLINT(runtime/int) - constexpr uint128(unsigned long v); // NOLINT(runtime/int) - constexpr uint128(long long v); // NOLINT(runtime/int) - constexpr uint128(unsigned long long v); // NOLINT(runtime/int) -#ifdef ABSL_HAVE_INTRINSIC_INT128 - constexpr uint128(__int128 v); // NOLINT(runtime/explicit) - constexpr uint128(unsigned __int128 v); // NOLINT(runtime/explicit) -#endif // ABSL_HAVE_INTRINSIC_INT128 - explicit uint128(float v); - explicit uint128(double v); - explicit uint128(long double v); - - // Assignment operators from arithmetic types - uint128& operator=(int v); - uint128& operator=(unsigned int v); - uint128& operator=(long v); // NOLINT(runtime/int) - uint128& operator=(unsigned long v); // NOLINT(runtime/int) - uint128& operator=(long long v); // NOLINT(runtime/int) - uint128& operator=(unsigned long long v); // NOLINT(runtime/int) -#ifdef ABSL_HAVE_INTRINSIC_INT128 - uint128& operator=(__int128 v); - uint128& operator=(unsigned __int128 v); -#endif // ABSL_HAVE_INTRINSIC_INT128 - - // Conversion operators to other arithmetic types - constexpr explicit operator bool() const; - constexpr explicit operator char() const; - constexpr explicit operator signed char() const; - constexpr explicit operator unsigned char() const; - constexpr explicit operator char16_t() const; - constexpr explicit operator char32_t() const; - constexpr explicit operator wchar_t() const; - constexpr explicit operator short() const; // NOLINT(runtime/int) - // NOLINTNEXTLINE(runtime/int) - constexpr explicit operator unsigned short() const; - constexpr explicit operator int() const; - constexpr explicit operator unsigned int() const; - constexpr explicit operator long() const; // NOLINT(runtime/int) - // NOLINTNEXTLINE(runtime/int) - constexpr explicit operator unsigned long() const; - // NOLINTNEXTLINE(runtime/int) - constexpr explicit operator long long() const; - // NOLINTNEXTLINE(runtime/int) - constexpr explicit operator unsigned long long() const; -#ifdef ABSL_HAVE_INTRINSIC_INT128 - constexpr explicit operator __int128() const; - constexpr explicit operator unsigned __int128() const; -#endif // ABSL_HAVE_INTRINSIC_INT128 - explicit operator float() const; - explicit operator double() const; - explicit operator long double() const; - - // Trivial copy constructor, assignment operator and destructor. - - // Arithmetic operators. - uint128& operator+=(uint128 other); - uint128& operator-=(uint128 other); - uint128& operator*=(uint128 other); - // Long division/modulo for uint128. - uint128& operator/=(uint128 other); - uint128& operator%=(uint128 other); - uint128 operator++(int); - uint128 operator--(int); - uint128& operator<<=(int); - uint128& operator>>=(int); - uint128& operator&=(uint128 other); - uint128& operator|=(uint128 other); - uint128& operator^=(uint128 other); - uint128& operator++(); - uint128& operator--(); - - // Uint128Low64() - // - // Returns the lower 64-bit value of a `uint128` value. - friend constexpr uint64_t Uint128Low64(uint128 v); - - // Uint128High64() - // - // Returns the higher 64-bit value of a `uint128` value. - friend constexpr uint64_t Uint128High64(uint128 v); - - // MakeUInt128() - // - // Constructs a `uint128` numeric value from two 64-bit unsigned integers. - // Note that this factory function is the only way to construct a `uint128` - // from integer values greater than 2^64. - // - // Example: - // - // absl::uint128 big = absl::MakeUint128(1, 0); - friend constexpr uint128 MakeUint128(uint64_t high, uint64_t low); - - // Uint128Max() - // - // Returns the highest value for a 128-bit unsigned integer. - friend constexpr uint128 Uint128Max(); - - private: - constexpr uint128(uint64_t high, uint64_t low); - - // TODO(strel) Update implementation to use __int128 once all users of - // uint128 are fixed to not depend on alignof(uint128) == 8. Also add - // alignas(16) to class definition to keep alignment consistent across - // platforms. -#if defined(ABSL_IS_LITTLE_ENDIAN) - uint64_t lo_; - uint64_t hi_; -#elif defined(ABSL_IS_BIG_ENDIAN) - uint64_t hi_; - uint64_t lo_; -#else // byte order -#error "Unsupported byte order: must be little-endian or big-endian." -#endif // byte order -}; - -// Prefer to use the constexpr `Uint128Max()`. -// -// TODO(absl-team) deprecate kuint128max once migration tool is released. -extern const uint128 kuint128max; - -// allow uint128 to be logged -std::ostream& operator<<(std::ostream& os, uint128 v); - -// TODO(strel) add operator>>(std::istream&, uint128) - -// TODO(absl-team): Implement signed 128-bit type - -// -------------------------------------------------------------------------- -// Implementation details follow -// -------------------------------------------------------------------------- - -constexpr uint128 MakeUint128(uint64_t high, uint64_t low) { - return uint128(high, low); -} - -constexpr uint128 Uint128Max() { - return uint128(std::numeric_limits::max(), - std::numeric_limits::max()); -} - -// Assignment from integer types. - -inline uint128& uint128::operator=(int v) { return *this = uint128(v); } - -inline uint128& uint128::operator=(unsigned int v) { - return *this = uint128(v); -} - -inline uint128& uint128::operator=(long v) { // NOLINT(runtime/int) - return *this = uint128(v); -} - -// NOLINTNEXTLINE(runtime/int) -inline uint128& uint128::operator=(unsigned long v) { - return *this = uint128(v); -} - -// NOLINTNEXTLINE(runtime/int) -inline uint128& uint128::operator=(long long v) { - return *this = uint128(v); -} - -// NOLINTNEXTLINE(runtime/int) -inline uint128& uint128::operator=(unsigned long long v) { - return *this = uint128(v); -} - -#ifdef ABSL_HAVE_INTRINSIC_INT128 -inline uint128& uint128::operator=(__int128 v) { - return *this = uint128(v); -} - -inline uint128& uint128::operator=(unsigned __int128 v) { - return *this = uint128(v); -} -#endif // ABSL_HAVE_INTRINSIC_INT128 - -// Shift and arithmetic operators. - -inline uint128 operator<<(uint128 lhs, int amount) { return lhs <<= amount; } - -inline uint128 operator>>(uint128 lhs, int amount) { return lhs >>= amount; } - -inline uint128 operator+(uint128 lhs, uint128 rhs) { return lhs += rhs; } - -inline uint128 operator-(uint128 lhs, uint128 rhs) { return lhs -= rhs; } - -inline uint128 operator*(uint128 lhs, uint128 rhs) { return lhs *= rhs; } - -inline uint128 operator/(uint128 lhs, uint128 rhs) { return lhs /= rhs; } - -inline uint128 operator%(uint128 lhs, uint128 rhs) { return lhs %= rhs; } - -constexpr uint64_t Uint128Low64(uint128 v) { return v.lo_; } - -constexpr uint64_t Uint128High64(uint128 v) { return v.hi_; } - -// Constructors from integer types. - -#if defined(ABSL_IS_LITTLE_ENDIAN) - -constexpr uint128::uint128(uint64_t high, uint64_t low) - : lo_{low}, hi_{high} {} - -constexpr uint128::uint128(int v) - : lo_{static_cast(v)}, - hi_{v < 0 ? std::numeric_limits::max() : 0} {} -constexpr uint128::uint128(long v) // NOLINT(runtime/int) - : lo_{static_cast(v)}, - hi_{v < 0 ? std::numeric_limits::max() : 0} {} -constexpr uint128::uint128(long long v) // NOLINT(runtime/int) - : lo_{static_cast(v)}, - hi_{v < 0 ? std::numeric_limits::max() : 0} {} - -constexpr uint128::uint128(unsigned int v) : lo_{v}, hi_{0} {} -// NOLINTNEXTLINE(runtime/int) -constexpr uint128::uint128(unsigned long v) : lo_{v}, hi_{0} {} -// NOLINTNEXTLINE(runtime/int) -constexpr uint128::uint128(unsigned long long v) : lo_{v}, hi_{0} {} - -#ifdef ABSL_HAVE_INTRINSIC_INT128 -constexpr uint128::uint128(__int128 v) - : lo_{static_cast(v & ~uint64_t{0})}, - hi_{static_cast(static_cast(v) >> 64)} {} -constexpr uint128::uint128(unsigned __int128 v) - : lo_{static_cast(v & ~uint64_t{0})}, - hi_{static_cast(v >> 64)} {} -#endif // ABSL_HAVE_INTRINSIC_INT128 - -#elif defined(ABSL_IS_BIG_ENDIAN) - -constexpr uint128::uint128(uint64_t high, uint64_t low) - : hi_{high}, lo_{low} {} - -constexpr uint128::uint128(int v) - : hi_{v < 0 ? std::numeric_limits::max() : 0}, - lo_{static_cast(v)} {} -constexpr uint128::uint128(long v) // NOLINT(runtime/int) - : hi_{v < 0 ? std::numeric_limits::max() : 0}, - lo_{static_cast(v)} {} -constexpr uint128::uint128(long long v) // NOLINT(runtime/int) - : hi_{v < 0 ? std::numeric_limits::max() : 0}, - lo_{static_cast(v)} {} - -constexpr uint128::uint128(unsigned int v) : hi_{0}, lo_{v} {} -// NOLINTNEXTLINE(runtime/int) -constexpr uint128::uint128(unsigned long v) : hi_{0}, lo_{v} {} -// NOLINTNEXTLINE(runtime/int) -constexpr uint128::uint128(unsigned long long v) : hi_{0}, lo_{v} {} - -#ifdef ABSL_HAVE_INTRINSIC_INT128 -constexpr uint128::uint128(__int128 v) - : hi_{static_cast(static_cast(v) >> 64)}, - lo_{static_cast(v & ~uint64_t{0})} {} -constexpr uint128::uint128(unsigned __int128 v) - : hi_{static_cast(v >> 64)}, - lo_{static_cast(v & ~uint64_t{0})} {} -#endif // ABSL_HAVE_INTRINSIC_INT128 - -#else // byte order -#error "Unsupported byte order: must be little-endian or big-endian." -#endif // byte order - -// Conversion operators to integer types. - -constexpr uint128::operator bool() const { return lo_ || hi_; } - -constexpr uint128::operator char() const { return static_cast(lo_); } - -constexpr uint128::operator signed char() const { - return static_cast(lo_); -} - -constexpr uint128::operator unsigned char() const { - return static_cast(lo_); -} - -constexpr uint128::operator char16_t() const { - return static_cast(lo_); -} - -constexpr uint128::operator char32_t() const { - return static_cast(lo_); -} - -constexpr uint128::operator wchar_t() const { - return static_cast(lo_); -} - -// NOLINTNEXTLINE(runtime/int) -constexpr uint128::operator short() const { return static_cast(lo_); } - -constexpr uint128::operator unsigned short() const { // NOLINT(runtime/int) - return static_cast(lo_); // NOLINT(runtime/int) -} - -constexpr uint128::operator int() const { return static_cast(lo_); } - -constexpr uint128::operator unsigned int() const { - return static_cast(lo_); -} - -// NOLINTNEXTLINE(runtime/int) -constexpr uint128::operator long() const { return static_cast(lo_); } - -constexpr uint128::operator unsigned long() const { // NOLINT(runtime/int) - return static_cast(lo_); // NOLINT(runtime/int) -} - -constexpr uint128::operator long long() const { // NOLINT(runtime/int) - return static_cast(lo_); // NOLINT(runtime/int) -} - -constexpr uint128::operator unsigned long long() const { // NOLINT(runtime/int) - return static_cast(lo_); // NOLINT(runtime/int) -} - -#ifdef ABSL_HAVE_INTRINSIC_INT128 -constexpr uint128::operator __int128() const { - return (static_cast<__int128>(hi_) << 64) + lo_; -} - -constexpr uint128::operator unsigned __int128() const { - return (static_cast(hi_) << 64) + lo_; -} -#endif // ABSL_HAVE_INTRINSIC_INT128 - -// Conversion operators to floating point types. - -inline uint128::operator float() const { - return static_cast(lo_) + std::ldexp(static_cast(hi_), 64); -} - -inline uint128::operator double() const { - return static_cast(lo_) + std::ldexp(static_cast(hi_), 64); -} - -inline uint128::operator long double() const { - return static_cast(lo_) + - std::ldexp(static_cast(hi_), 64); -} - -// Comparison operators. - -inline bool operator==(uint128 lhs, uint128 rhs) { - return (Uint128Low64(lhs) == Uint128Low64(rhs) && - Uint128High64(lhs) == Uint128High64(rhs)); -} - -inline bool operator!=(uint128 lhs, uint128 rhs) { - return !(lhs == rhs); -} - -inline bool operator<(uint128 lhs, uint128 rhs) { - return (Uint128High64(lhs) == Uint128High64(rhs)) - ? (Uint128Low64(lhs) < Uint128Low64(rhs)) - : (Uint128High64(lhs) < Uint128High64(rhs)); -} - -inline bool operator>(uint128 lhs, uint128 rhs) { - return (Uint128High64(lhs) == Uint128High64(rhs)) - ? (Uint128Low64(lhs) > Uint128Low64(rhs)) - : (Uint128High64(lhs) > Uint128High64(rhs)); -} - -inline bool operator<=(uint128 lhs, uint128 rhs) { - return (Uint128High64(lhs) == Uint128High64(rhs)) - ? (Uint128Low64(lhs) <= Uint128Low64(rhs)) - : (Uint128High64(lhs) <= Uint128High64(rhs)); -} - -inline bool operator>=(uint128 lhs, uint128 rhs) { - return (Uint128High64(lhs) == Uint128High64(rhs)) - ? (Uint128Low64(lhs) >= Uint128Low64(rhs)) - : (Uint128High64(lhs) >= Uint128High64(rhs)); -} - -// Unary operators. - -inline uint128 operator-(uint128 val) { - uint64_t hi = ~Uint128High64(val); - uint64_t lo = ~Uint128Low64(val) + 1; - if (lo == 0) ++hi; // carry - return MakeUint128(hi, lo); -} - -inline bool operator!(uint128 val) { - return !Uint128High64(val) && !Uint128Low64(val); -} - -// Logical operators. - -inline uint128 operator~(uint128 val) { - return MakeUint128(~Uint128High64(val), ~Uint128Low64(val)); -} - -inline uint128 operator|(uint128 lhs, uint128 rhs) { - return MakeUint128(Uint128High64(lhs) | Uint128High64(rhs), - Uint128Low64(lhs) | Uint128Low64(rhs)); -} - -inline uint128 operator&(uint128 lhs, uint128 rhs) { - return MakeUint128(Uint128High64(lhs) & Uint128High64(rhs), - Uint128Low64(lhs) & Uint128Low64(rhs)); -} - -inline uint128 operator^(uint128 lhs, uint128 rhs) { - return MakeUint128(Uint128High64(lhs) ^ Uint128High64(rhs), - Uint128Low64(lhs) ^ Uint128Low64(rhs)); -} - -inline uint128& uint128::operator|=(uint128 other) { - hi_ |= other.hi_; - lo_ |= other.lo_; - return *this; -} - -inline uint128& uint128::operator&=(uint128 other) { - hi_ &= other.hi_; - lo_ &= other.lo_; - return *this; -} - -inline uint128& uint128::operator^=(uint128 other) { - hi_ ^= other.hi_; - lo_ ^= other.lo_; - return *this; -} - -// Shift and arithmetic assign operators. - -inline uint128& uint128::operator<<=(int amount) { - assert(amount >= 0); // Negative shifts are undefined. - assert(amount < 128); // Shifts of >= 128 are undefined. - - // uint64_t shifts of >= 64 are undefined, so we will need some - // special-casing. - if (amount < 64) { - if (amount != 0) { - hi_ = (hi_ << amount) | (lo_ >> (64 - amount)); - lo_ = lo_ << amount; - } - } else { - hi_ = lo_ << (amount - 64); - lo_ = 0; - } - return *this; -} - -inline uint128& uint128::operator>>=(int amount) { - assert(amount >= 0); // Negative shifts are undefined. - assert(amount < 128); // Shifts of >= 128 are undefined. - - // uint64_t shifts of >= 64 are undefined, so we will need some - // special-casing. - if (amount < 64) { - if (amount != 0) { - lo_ = (lo_ >> amount) | (hi_ << (64 - amount)); - hi_ = hi_ >> amount; - } - } else { - lo_ = hi_ >> (amount - 64); - hi_ = 0; - } - return *this; -} - -inline uint128& uint128::operator+=(uint128 other) { - hi_ += other.hi_; - uint64_t lolo = lo_ + other.lo_; - if (lolo < lo_) - ++hi_; - lo_ = lolo; - return *this; -} - -inline uint128& uint128::operator-=(uint128 other) { - hi_ -= other.hi_; - if (other.lo_ > lo_) --hi_; - lo_ -= other.lo_; - return *this; -} - -inline uint128& uint128::operator*=(uint128 other) { -#if defined(ABSL_HAVE_INTRINSIC_INT128) - // TODO(strel) Remove once alignment issues are resolved and unsigned __int128 - // can be used for uint128 storage. - *this = static_cast(*this) * - static_cast(other); - return *this; -#else // ABSL_HAVE_INTRINSIC128 - uint64_t a32 = lo_ >> 32; - uint64_t a00 = lo_ & 0xffffffff; - uint64_t b32 = other.lo_ >> 32; - uint64_t b00 = other.lo_ & 0xffffffff; - hi_ = hi_ * other.lo_ + lo_ * other.hi_ + a32 * b32; - lo_ = a00 * b00; - *this += uint128(a32 * b00) << 32; - *this += uint128(a00 * b32) << 32; - return *this; -#endif // ABSL_HAVE_INTRINSIC128 -} - -// Increment/decrement operators. - -inline uint128 uint128::operator++(int) { - uint128 tmp(*this); - *this += 1; - return tmp; -} - -inline uint128 uint128::operator--(int) { - uint128 tmp(*this); - *this -= 1; - return tmp; -} - -inline uint128& uint128::operator++() { - *this += 1; - return *this; -} - -inline uint128& uint128::operator--() { - *this -= 1; - return *this; -} - -#if defined(ABSL_HAVE_INTRINSIC_INT128) -#include "absl/numeric/int128_have_intrinsic.inc" -#else // ABSL_HAVE_INTRINSIC_INT128 -#include "absl/numeric/int128_no_intrinsic.inc" -#endif // ABSL_HAVE_INTRINSIC_INT128 - -} // namespace absl - -#endif // ABSL_NUMERIC_INT128_H_ diff --git a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt.orig b/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt.orig deleted file mode 100644 index b1426eeba45..00000000000 --- a/Firestore/third_party/abseil-cpp/absl/strings/CMakeLists.txt.orig +++ /dev/null @@ -1,249 +0,0 @@ -# -# Copyright 2017 The Abseil Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - - -list(APPEND STRINGS_PUBLIC_HEADERS - "ascii.h" - "match.h" -<<<<<<< HEAD - "numbers.h" - "str_cat.h" -======= - "str_join.h" - "str_replace.h" - "str_split.h" ->>>>>>> add new files to projects - "string_view.h" - "strip.h" - "str_join.h" - "str_replace.h" - "str_split.h" -) - - -list(APPEND STRINGS_INTERNAL_HEADERS - "internal/memutil.h" - "internal/ostringstream.h" - "internal/resize_uninitialized.h" - "internal/stl_type_traits.h" - "internal/str_join_internal.h" - "internal/str_split_internal.h" -) - - - -# add string library -list(APPEND STRINGS_SRC - "ascii.cc" - "internal/memutil.cc" - "internal/memutil.h" - "internal/ostringstream.cc" - "match.cc" -<<<<<<< HEAD - "numbers.cc" - "str_cat.cc" -======= - "str_join.cc" ->>>>>>> add new files to projects - "str_replace.cc" - "str_split.cc" - "string_view.cc" - ${STRINGS_PUBLIC_HEADERS} - ${STRINGS_INTERNAL_HEADERS} -) -set(STRINGS_PUBLIC_LIBRARIES absl::base absl_throw_delegate) - -absl_library( - TARGET - absl_strings - SOURCES - ${STRINGS_SRC} - PUBLIC_LIBRARIES - ${STRINGS_PUBLIC_LIBRARIES} - EXPORT_NAME - strings -) - - -# -## TESTS -# - -# test match_test -set(MATCH_TEST_SRC "match_test.cc") -set(MATCH_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - match_test - SOURCES - ${MATCH_TEST_SRC} - PUBLIC_LIBRARIES - ${MATCH_TEST_PUBLIC_LIBRARIES} -) - - -# test ascii_test -set(ASCII_TEST_SRC "ascii_test.cc") -set(ASCII_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - ascii_test - SOURCES - ${ASCII_TEST_SRC} - PUBLIC_LIBRARIES - ${ASCII_TEST_PUBLIC_LIBRARIES} -) - - -# test memutil_test -set(MEMUTIL_TEST_SRC "internal/memutil_test.cc") -set(MEMUTIL_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - memutil_test - SOURCES - ${MEMUTIL_TEST_SRC} - PUBLIC_LIBRARIES - ${MEMUTIL_TEST_PUBLIC_LIBRARIES} -) - - -# test string_view_test -set(STRING_VIEW_TEST_SRC "string_view_test.cc") -set(STRING_VIEW_TEST_PUBLIC_LIBRARIES absl::strings absl_throw_delegate absl::base) - -absl_test( - TARGET - string_view_test - SOURCES - ${STRING_VIEW_TEST_SRC} - PUBLIC_LIBRARIES - ${STRING_VIEW_TEST_PUBLIC_LIBRARIES} -) - - -# test str_replace_test -set(STR_REPLACE_TEST_SRC "str_replace_test.cc") -set(STR_REPLACE_TEST_PUBLIC_LIBRARIES absl::strings absl::base absl_throw_delegate) - -absl_test( - TARGET - str_replace_test - SOURCES - ${STR_REPLACE_TEST_SRC} - PUBLIC_LIBRARIES - ${STR_REPLACE_TEST_PUBLIC_LIBRARIES} -) - - -# test str_split_test -set(STR_SPLIT_TEST_SRC "str_split_test.cc") -set(STR_SPLIT_TEST_PUBLIC_LIBRARIES absl::strings absl::base absl_throw_delegate) - -absl_test( - TARGET - str_split_test - SOURCES - ${STR_SPLIT_TEST_SRC} - PUBLIC_LIBRARIES - ${STR_SPLIT_TEST_PUBLIC_LIBRARIES} -) - - -# test ostringstream_test -set(OSTRINGSTREAM_TEST_SRC "internal/ostringstream_test.cc") -set(OSTRINGSTREAM_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - ostringstream_test - SOURCES - ${OSTRINGSTREAM_TEST_SRC} - PUBLIC_LIBRARIES - ${OSTRINGSTREAM_TEST_PUBLIC_LIBRARIES} -) - - -# test resize_uninitialized_test -set(RESIZE_UNINITIALIZED_TEST_SRC "internal/resize_uninitialized_test.cc") - -absl_test( - TARGET - resize_uninitialized_test - SOURCES - ${RESIZE_UNINITIALIZED_TEST_SRC} -) - - -# test str_join_test -set(STR_JOIN_TEST_SRC "str_join_test.cc") -set(STR_JOIN_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - str_join_test - SOURCES - ${STR_JOIN_TEST_SRC} - PUBLIC_LIBRARIES - ${STR_JOIN_TEST_PUBLIC_LIBRARIES} -) - - -# test str_cat_test -set(STR_CAT_TEST_SRC "str_cat_test.cc") -set(STR_CAT_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - str_cat_test - SOURCES - ${STR_CAT_TEST_SRC} - PUBLIC_LIBRARIES - ${STR_CAT_TEST_PUBLIC_LIBRARIES} -) - - -# test numbers_test -set(NUMBERS_TEST_SRC "numbers_test.cc") -set(NUMBERS_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - numbers_test - SOURCES - ${NUMBERS_TEST_SRC} - PUBLIC_LIBRARIES - ${NUMBERS_TEST_PUBLIC_LIBRARIES} -) - - -# test strip_test -set(STRIP_TEST_SRC "strip_test.cc") -set(STRIP_TEST_PUBLIC_LIBRARIES absl::strings) - -absl_test( - TARGET - strip_test - SOURCES - ${STRIP_TEST_SRC} - PUBLIC_LIBRARIES - ${STRIP_TEST_PUBLIC_LIBRARIES} -) - - From bb8006c6a4bb71abfb726f45369ec001376c18c6 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Tue, 6 Feb 2018 17:13:43 -0500 Subject: [PATCH 39/48] C++ port: add C++ equivalent of FSTDocumentKey. Also move kDocumentKeyPath to the only point of usage - make it a static member variable of FieldPath. --- .../Firestore.xcodeproj/project.pbxproj | 4 + .../firebase/firestore/model/document_key.cc | 52 ++++++ .../firebase/firestore/model/document_key.h | 95 +++++++++++ .../firebase/firestore/model/field_path.cc | 7 +- .../src/firebase/firestore/model/field_path.h | 2 + .../firebase/firestore/model/resource_path.cc | 2 +- .../firebase/firestore/model/resource_path.h | 2 +- .../firestore/model/document_key_test.cc | 148 ++++++++++++++++++ .../firestore/model/resource_path_test.cc | 8 +- 9 files changed, 309 insertions(+), 11 deletions(-) create mode 100644 Firestore/core/src/firebase/firestore/model/document_key.cc create mode 100644 Firestore/core/src/firebase/firestore/model/document_key.h create mode 100644 Firestore/core/test/firebase/firestore/model/document_key_test.cc diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 8d99eb6aa7c..13e75da2e2d 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -144,6 +144,7 @@ ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; }; + B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; }; @@ -340,6 +341,7 @@ AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = geo_point_test.cc; path = ../../core/test/firebase/firestore/geo_point_test.cc; sourceTree = ""; }; ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B6152AD5202A5385000E5744 /* document_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document_key_test.cc; sourceTree = ""; }; B686F2AD2023DDB20028D6BE /* field_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_path_test.cc; sourceTree = ""; }; B686F2B02024FFD70028D6BE /* resource_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resource_path_test.cc; sourceTree = ""; }; CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = ""; }; @@ -569,6 +571,7 @@ AB356EF5200E9D1A0089B766 /* model */ = { isa = PBXGroup; children = ( + B6152AD5202A5385000E5744 /* document_key_test.cc */, B686F2B02024FFD70028D6BE /* resource_path_test.cc */, B686F2AD2023DDB20028D6BE /* field_path_test.cc */, AB71064B201FA60300344F18 /* database_id_test.cc */, @@ -1290,6 +1293,7 @@ 5492E0C82021557E00B64F25 /* FSTDatastoreTests.mm in Sources */, 5492E065202154B900B64F25 /* FSTViewTests.mm in Sources */, 5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */, + B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */, 54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */, AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */, 5492E03F2021401F00B64F25 /* FSTHelpers.mm in Sources */, diff --git a/Firestore/core/src/firebase/firestore/model/document_key.cc b/Firestore/core/src/firebase/firestore/model/document_key.cc new file mode 100644 index 00000000000..bc60d99cd36 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_key.cc @@ -0,0 +1,52 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/document_key.h" + +#include + +#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" + +namespace firebase { +namespace firestore { +namespace model { + +namespace { + +void AssertValidPath(const ResourcePath& path) { + FIREBASE_ASSERT_MESSAGE(DocumentKey::IsDocumentKey(path), + "invalid document key path: %s", + path.CanonicalString().c_str()); +} + +} // namespace + +DocumentKey::DocumentKey(const ResourcePath& path) : path_{path} { + AssertValidPath(path_); +} + +DocumentKey::DocumentKey(ResourcePath&& path) : path_{std::move(path)} { + AssertValidPath(path_); +} + +const DocumentKey& DocumentKey::Empty() { + static DocumentKey empty; + return empty; +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h new file mode 100644 index 00000000000..f26cd9cb624 --- /dev/null +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -0,0 +1,95 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_ +#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_ + +#include +#include + +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace model { + +class DocumentKey { + public: + DocumentKey() = default; + /** Creates and returns a new document key with a copy of the given path. */ + explicit DocumentKey(const ResourcePath& path); + /** + * Creates and returns a new document key, taking ownership of the given + * path. + */ + explicit DocumentKey(ResourcePath&& path); + + /** + * Creates and returns a new document key using '/' to split the string into + * segments. + */ + static DocumentKey FromPathString(absl::string_view path) { + return DocumentKey{ResourcePath::FromString(path)}; + } + /** + * Creates and returns a new document key with the given segments. + */ + static DocumentKey FromSegments(std::initializer_list list) { + return DocumentKey{ResourcePath{list}}; + } + + /** + * Returns a shared instance of an empty document key. + */ + static const DocumentKey& Empty(); + + static bool IsDocumentKey(const ResourcePath& path) { + return path.size() % 2 == 0; + } + + const ResourcePath& path() const { + return path_; + } + + bool operator==(const DocumentKey& rhs) const { + return path_ == rhs.path_; + } + bool operator!=(const DocumentKey& rhs) const { + return path_ != rhs.path_; + } + bool operator<(const DocumentKey& rhs) const { + return path_ < rhs.path_; + } + bool operator<=(const DocumentKey& rhs) const { + return path_ <= rhs.path_; + } + bool operator>(const DocumentKey& rhs) const { + return path_ > rhs.path_; + } + bool operator>=(const DocumentKey& rhs) const { + return path_ >= rhs.path_; + } + + private: + ResourcePath path_; +}; + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_ diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 6c406001747..aa38298b61a 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -30,9 +30,6 @@ namespace model { namespace { -// TODO(varconst): move to C++ equivalent of FSTDocumentKey.{h,cc} -const char* const kDocumentKeyPath = "__name__"; - /** * True if the string could be used as a segment in a field path without * escaping. Valid identifies follow the regex [a-zA-Z_][a-zA-Z0-9_]* @@ -146,12 +143,12 @@ const FieldPath& FieldPath::EmptyPath() { } const FieldPath& FieldPath::KeyFieldPath() { - static const FieldPath key_field_path{kDocumentKeyPath}; + static const FieldPath key_field_path{FieldPath::kDocumentKeyPath}; return key_field_path; } bool FieldPath::IsKeyFieldPath() const { - return size() == 1 && first_segment() == kDocumentKeyPath; + return size() == 1 && first_segment() == FieldPath::kDocumentKeyPath; } std::string FieldPath::CanonicalString() const { diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 33423d76d5a..448e7108a20 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -35,6 +35,8 @@ namespace model { */ class FieldPath : public impl::BasePath { public: + static constexpr const char* kDocumentKeyPath = "__name__"; + FieldPath() = default; /** Constructs the path from segments. */ template diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.cc b/Firestore/core/src/firebase/firestore/model/resource_path.cc index 36218e98dd6..f1a3d38d8bb 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.cc +++ b/Firestore/core/src/firebase/firestore/model/resource_path.cc @@ -27,7 +27,7 @@ namespace firebase { namespace firestore { namespace model { -ResourcePath ResourcePath::Parse(const absl::string_view path) { +ResourcePath ResourcePath::FromString(const absl::string_view path) { // NOTE: The client is ignorant of any path segments containing escape // sequences (e.g. __id123__) and just passes them through raw (they exist // for legacy reasons and should not be used frequently). diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index d5a19cea491..16edb78f210 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -44,7 +44,7 @@ class ResourcePath : public impl::BasePath { * Creates and returns a new path from the given resource-path string, where * the path segments are separated by a slash "/". */ - static ResourcePath Parse(absl::string_view path); + static ResourcePath FromString(absl::string_view path); /** Returns a standardized string representation of this path. */ std::string CanonicalString() const; diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc new file mode 100644 index 00000000000..c2651c8f235 --- /dev/null +++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc @@ -0,0 +1,148 @@ +/* + * Copyright 2018 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Firestore/core/src/firebase/firestore/model/document_key.h" + +#include +#include +#include +#include + +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +TEST(DocumentKey, Constructor_Empty) { + const DocumentKey default_key; + EXPECT_TRUE(default_key.path().empty()); + + const auto& empty_key = DocumentKey::Empty(); + const auto& another_empty_key = DocumentKey::Empty(); + EXPECT_EQ(default_key, empty_key); + EXPECT_EQ(empty_key, another_empty_key); + EXPECT_EQ(&empty_key, &another_empty_key); +} + +TEST(DocumentKey, Constructor_FromPath) { + ResourcePath path{"rooms", "firestore", "messages", "1"}; + const DocumentKey key_from_path_copy{path}; + // path shouldn't have been moved from. + EXPECT_FALSE(path.empty()); + EXPECT_EQ(key_from_path_copy.path(), path); + + const DocumentKey key_from_moved_path{std::move(path)}; + EXPECT_TRUE(path.empty()); + EXPECT_FALSE(key_from_moved_path.path().empty()); + EXPECT_EQ(key_from_path_copy.path(), key_from_moved_path.path()); +} + +TEST(DocumentKey, CopyAndMove) { + DocumentKey key({"rooms", "firestore", "messages", "1"}); + const std::string path_string = "rooms/firestore/messages/1"; + EXPECT_EQ(path_string, key.path().CanonicalString()); + + auto copied = key; + EXPECT_EQ(path_string, copied.path().CanonicalString()); + EXPECT_EQ(key, copied); + + const auto moved = std::move(key); + EXPECT_EQ(path_string, moved.path().CanonicalString()); + EXPECT_NE(key, moved); + EXPECT_TRUE(key.path().empty()); + + // Reassignment. + + key = copied; + EXPECT_EQ(copied, key); + EXPECT_EQ(path_string, key.path().CanonicalString()); + + key = {}; + EXPECT_TRUE(key.path().empty()); + key = std::move(copied); + EXPECT_NE(copied, key); + EXPECT_TRUE(copied.path().empty()); + EXPECT_EQ(path_string, key.path().CanonicalString()); +} + +TEST(DocumentKey, Constructor_StaticFactory) { + const auto key_from_segments = + DocumentKey::FromSegments({"rooms", "firestore", "messages", "1"}); + const std::string path_string = "rooms/firestore/messages/1"; + const auto key_from_string = DocumentKey::FromPathString(path_string); + EXPECT_EQ(path_string, key_from_string.path().CanonicalString()); + EXPECT_EQ(path_string, key_from_segments.path().CanonicalString()); + EXPECT_EQ(key_from_segments, key_from_string); +} + +TEST(DocumentKey, Constructor_BadArguments) { + ASSERT_DEATH_IF_SUPPORTED(DocumentKey(ResourcePath{"foo"}), ""); + ASSERT_DEATH_IF_SUPPORTED(DocumentKey(ResourcePath{"foo", "bar", "baz"}), ""); + + ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromSegments({"foo"}), ""); + ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromSegments({"foo", "bar", "baz"}), + ""); + + ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString(""), ""); + ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid//string"), ""); + ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid/key/path"), + ""); +} + +TEST(DocumentKey, IsDocumentKey) { + EXPECT_TRUE(DocumentKey::IsDocumentKey({})); + EXPECT_FALSE(DocumentKey::IsDocumentKey({"foo"})); + EXPECT_TRUE(DocumentKey::IsDocumentKey({"foo", "bar"})); + EXPECT_FALSE(DocumentKey::IsDocumentKey({"foo", "bar", "baz"})); +} + +TEST(DocumentKey, Comparison) { + const DocumentKey abcd({"a", "b", "c", "d"}); + const DocumentKey abcd_too({"a", "b", "c", "d"}); + const DocumentKey xyzw({"x", "y", "z", "w"}); + EXPECT_EQ(abcd, abcd_too); + EXPECT_NE(abcd, xyzw); + + const DocumentKey empty; + const DocumentKey a({"a", "a"}); + const DocumentKey b({"b", "b"}); + const DocumentKey ab({"a", "a", "b", "b"}); + + EXPECT_TRUE(empty < a); + EXPECT_TRUE(empty <= a); + EXPECT_TRUE(a > empty); + EXPECT_TRUE(a >= empty); + + EXPECT_FALSE(a < a); + EXPECT_TRUE(a <= a); + EXPECT_FALSE(a > a); + EXPECT_TRUE(a >= a); + + EXPECT_TRUE(a < b); + EXPECT_TRUE(a <= b); + EXPECT_TRUE(b > a); + EXPECT_TRUE(b >= a); + + EXPECT_TRUE(a < ab); + EXPECT_TRUE(a <= ab); + EXPECT_TRUE(ab > a); + EXPECT_TRUE(ab >= a); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc index 317a1dbeea4..570c3ab8dbb 100644 --- a/Firestore/core/test/firebase/firestore/model/resource_path_test.cc +++ b/Firestore/core/test/firebase/firestore/model/resource_path_test.cc @@ -74,7 +74,7 @@ TEST(ResourcePath, Comparison) { TEST(ResourcePath, Parsing) { const auto parse = [](const std::pair expected) { - const auto path = ResourcePath::Parse(expected.first); + const auto path = ResourcePath::FromString(expected.first); return std::make_pair(path.CanonicalString(), path.size()); }; const auto make_expected = [](const std::string& str, const size_t size) { @@ -92,12 +92,12 @@ TEST(ResourcePath, Parsing) { expected = make_expected(R"(foo/__!?#@..`..\`/baz)", 3); EXPECT_EQ(expected, parse(expected)); - EXPECT_EQ(ResourcePath::Parse("/foo/").CanonicalString(), "foo"); + EXPECT_EQ(ResourcePath::FromString("/foo/").CanonicalString(), "foo"); } TEST(ResourcePath, ParseFailures) { - ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse("//"), ""); - ASSERT_DEATH_IF_SUPPORTED(ResourcePath::Parse("foo//bar"), ""); + ASSERT_DEATH_IF_SUPPORTED(ResourcePath::FromString("//"), ""); + ASSERT_DEATH_IF_SUPPORTED(ResourcePath::FromString("foo//bar"), ""); } } // namespace model From d672a0a8f10daa36d3f41179100e73ee03d149b4 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Tue, 6 Feb 2018 19:09:31 -0500 Subject: [PATCH 40/48] Review feedback --- .../firebase/firestore/model/document_key.cc | 19 ++++++++++++++ .../firebase/firestore/model/document_key.h | 26 +++++-------------- .../firestore/model/document_key_test.cc | 9 +++++-- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/document_key.cc b/Firestore/core/src/firebase/firestore/model/document_key.cc index bc60d99cd36..9cd30d2eaf4 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.cc +++ b/Firestore/core/src/firebase/firestore/model/document_key.cc @@ -47,6 +47,25 @@ const DocumentKey& DocumentKey::Empty() { return empty; } +bool operator==(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() == rhs.path(); +} +bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() != rhs.path(); +} +bool operator<(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() < rhs.path(); +} +bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() <= rhs.path(); +} +bool operator>(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() > rhs.path(); +} +bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() >= rhs.path(); +} + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h index f26cd9cb624..ca7673897b0 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.h +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -65,29 +65,17 @@ class DocumentKey { return path_; } - bool operator==(const DocumentKey& rhs) const { - return path_ == rhs.path_; - } - bool operator!=(const DocumentKey& rhs) const { - return path_ != rhs.path_; - } - bool operator<(const DocumentKey& rhs) const { - return path_ < rhs.path_; - } - bool operator<=(const DocumentKey& rhs) const { - return path_ <= rhs.path_; - } - bool operator>(const DocumentKey& rhs) const { - return path_ > rhs.path_; - } - bool operator>=(const DocumentKey& rhs) const { - return path_ >= rhs.path_; - } - private: ResourcePath path_; }; +bool operator==(const DocumentKey& lhs, const DocumentKey& rhs); +bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs); +bool operator<(const DocumentKey& lhs, const DocumentKey& rhs); +bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs); +bool operator>(const DocumentKey& lhs, const DocumentKey& rhs); +bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs); + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc index c2651c8f235..d7a1a4261c9 100644 --- a/Firestore/core/test/firebase/firestore/model/document_key_test.cc +++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc @@ -56,11 +56,11 @@ TEST(DocumentKey, CopyAndMove) { const std::string path_string = "rooms/firestore/messages/1"; EXPECT_EQ(path_string, key.path().CanonicalString()); - auto copied = key; + DocumentKey copied = key; EXPECT_EQ(path_string, copied.path().CanonicalString()); EXPECT_EQ(key, copied); - const auto moved = std::move(key); + const DocumentKey moved = std::move(key); EXPECT_EQ(path_string, moved.path().CanonicalString()); EXPECT_NE(key, moved); EXPECT_TRUE(key.path().empty()); @@ -98,6 +98,7 @@ TEST(DocumentKey, Constructor_BadArguments) { ""); ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString(""), ""); + ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid"), ""); ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid//string"), ""); ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid/key/path"), ""); @@ -122,6 +123,8 @@ TEST(DocumentKey, Comparison) { const DocumentKey b({"b", "b"}); const DocumentKey ab({"a", "a", "b", "b"}); + EXPECT_FALSE(empty < empty); + EXPECT_TRUE(empty <= empty); EXPECT_TRUE(empty < a); EXPECT_TRUE(empty <= a); EXPECT_TRUE(a > empty); @@ -131,6 +134,8 @@ TEST(DocumentKey, Comparison) { EXPECT_TRUE(a <= a); EXPECT_FALSE(a > a); EXPECT_TRUE(a >= a); + EXPECT_TRUE(a == a); + EXPECT_FALSE(a != a); EXPECT_TRUE(a < b); EXPECT_TRUE(a <= b); From e10678759eea633d5c6585e5ba7e35ec742afa48 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Wed, 7 Feb 2018 15:01:38 -0500 Subject: [PATCH 41/48] merge leftover --- Firestore/core/src/firebase/firestore/model/field_path.cc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.cc b/Firestore/core/src/firebase/firestore/model/field_path.cc index 2b370851024..aa38298b61a 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.cc +++ b/Firestore/core/src/firebase/firestore/model/field_path.cc @@ -30,12 +30,6 @@ namespace model { namespace { -<<<<<<< HEAD -======= -// TODO(varconst): move to C++ equivalent of FSTDocumentKey.{h,cc} -const char* const kDocumentKeyPath = "__name__"; - ->>>>>>> master /** * True if the string could be used as a segment in a field path without * escaping. Valid identifies follow the regex [a-zA-Z_][a-zA-Z0-9_]* From 2ff6c49f77346bb130be95faaaf133ff3e642fa0 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Wed, 7 Feb 2018 15:02:09 -0500 Subject: [PATCH 42/48] Review feedback: make implementation of DocumentKey store a shared_ptr to the path --- .../firebase/firestore/model/document_key.cc | 30 +++++++++-- .../firebase/firestore/model/document_key.h | 51 +++++++++---------- .../src/firebase/firestore/model/field_path.h | 1 + .../firestore/model/document_key_test.cc | 1 + 4 files changed, 52 insertions(+), 31 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/document_key.cc b/Firestore/core/src/firebase/firestore/model/document_key.cc index 9cd30d2eaf4..798917bf1f9 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.cc +++ b/Firestore/core/src/firebase/firestore/model/document_key.cc @@ -18,6 +18,7 @@ #include +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" namespace firebase { @@ -34,12 +35,25 @@ void AssertValidPath(const ResourcePath& path) { } // namespace -DocumentKey::DocumentKey(const ResourcePath& path) : path_{path} { - AssertValidPath(path_); +DocumentKey::DocumentKey() : path_{std::make_shared()} { } -DocumentKey::DocumentKey(ResourcePath&& path) : path_{std::move(path)} { - AssertValidPath(path_); +DocumentKey::DocumentKey(const ResourcePath& path) + : path_{std::make_shared(path)} { + AssertValidPath(*path_); +} + +DocumentKey::DocumentKey(ResourcePath&& path) + : path_{std::make_shared(std::move(path))} { + AssertValidPath(*path_); +} + +DocumentKey DocumentKey::FromPathString(const absl::string_view path) { + return DocumentKey{ResourcePath::FromString(path)}; +} + +DocumentKey DocumentKey::FromSegments(std::initializer_list list) { + return DocumentKey{ResourcePath{list}}; } const DocumentKey& DocumentKey::Empty() { @@ -47,6 +61,14 @@ const DocumentKey& DocumentKey::Empty() { return empty; } +bool DocumentKey::IsDocumentKey(const ResourcePath& path) { + return path.size() % 2 == 0; +} + +const ResourcePath& DocumentKey::path() const { + return path_ ? *path_ : Empty().path(); +} + bool operator==(const DocumentKey& lhs, const DocumentKey& rhs) { return lhs.path() == rhs.path(); } diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h index ca7673897b0..3c9f809a6fe 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.h +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -18,55 +18,52 @@ #define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_ #include +#include #include -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "absl/strings/string_view.h" namespace firebase { namespace firestore { namespace model { +class ResourcePath; + +/** + * DocumentKey represents the location of a document in the Firestore database. + */ class DocumentKey { public: - DocumentKey() = default; - /** Creates and returns a new document key with a copy of the given path. */ + /** Creates a "blank" document key not associated with any document. */ + DocumentKey(); + + /** Creates a new document key containing a copy of the given path. */ explicit DocumentKey(const ResourcePath& path); - /** - * Creates and returns a new document key, taking ownership of the given - * path. - */ + + /** Creates a new document key, taking ownership of the given path. */ explicit DocumentKey(ResourcePath&& path); /** * Creates and returns a new document key using '/' to split the string into * segments. */ - static DocumentKey FromPathString(absl::string_view path) { - return DocumentKey{ResourcePath::FromString(path)}; - } - /** - * Creates and returns a new document key with the given segments. - */ - static DocumentKey FromSegments(std::initializer_list list) { - return DocumentKey{ResourcePath{list}}; - } + static DocumentKey FromPathString(absl::string_view path); - /** - * Returns a shared instance of an empty document key. - */ - static const DocumentKey& Empty(); + /** Creates and returns a new document key with the given segments. */ + static DocumentKey FromSegments(std::initializer_list list); - static bool IsDocumentKey(const ResourcePath& path) { - return path.size() % 2 == 0; - } + /** Returns a shared instance of an empty document key. */ + static const DocumentKey& Empty(); - const ResourcePath& path() const { - return path_; - } + /** Returns true iff the given path is a path to a document. */ + static bool IsDocumentKey(const ResourcePath& path); + /** The path to the document. */ + const ResourcePath& path() const; private: - ResourcePath path_; + // This is an optimization to make passing DocumentKey around cheaper (it's + // copied often). + std::shared_ptr path_; }; bool operator==(const DocumentKey& lhs, const DocumentKey& rhs); diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 448e7108a20..3a051e6164c 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -35,6 +35,7 @@ namespace model { */ class FieldPath : public impl::BasePath { public: + /** The field path string that represents the document's key. */ static constexpr const char* kDocumentKeyPath = "__name__"; FieldPath() = default; diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc index d7a1a4261c9..22c21c3e715 100644 --- a/Firestore/core/test/firebase/firestore/model/document_key_test.cc +++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc @@ -15,6 +15,7 @@ */ #include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include #include From 6ae8395613b41bfc66da23ba6cde5bdc14c7999b Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Wed, 7 Feb 2018 15:05:30 -0500 Subject: [PATCH 43/48] style.sh --- Firestore/core/src/firebase/firestore/model/field_path.h | 2 +- Firestore/core/src/firebase/firestore/model/resource_path.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/field_path.h b/Firestore/core/src/firebase/firestore/model/field_path.h index 3a051e6164c..4556ebdf0b3 100644 --- a/Firestore/core/src/firebase/firestore/model/field_path.h +++ b/Firestore/core/src/firebase/firestore/model/field_path.h @@ -21,8 +21,8 @@ #include #include -#include "absl/strings/string_view.h" #include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "absl/strings/string_view.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/firebase/firestore/model/resource_path.h b/Firestore/core/src/firebase/firestore/model/resource_path.h index 16edb78f210..f2f96698c8a 100644 --- a/Firestore/core/src/firebase/firestore/model/resource_path.h +++ b/Firestore/core/src/firebase/firestore/model/resource_path.h @@ -20,8 +20,8 @@ #include #include -#include "absl/strings/string_view.h" #include "Firestore/core/src/firebase/firestore/model/base_path.h" +#include "absl/strings/string_view.h" namespace firebase { namespace firestore { From 491388994c4a2aaf2d9f4b021bb5ea6ed7626e41 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 9 Feb 2018 16:45:53 -0500 Subject: [PATCH 44/48] review comments --- .../firebase/firestore/model/document_key.cc | 41 +---------------- .../firebase/firestore/model/document_key.h | 45 ++++++++++++++----- 2 files changed, 35 insertions(+), 51 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/document_key.cc b/Firestore/core/src/firebase/firestore/model/document_key.cc index 798917bf1f9..ddda4c9d73d 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.cc +++ b/Firestore/core/src/firebase/firestore/model/document_key.cc @@ -18,7 +18,6 @@ #include -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "Firestore/core/src/firebase/firestore/util/firebase_assert.h" namespace firebase { @@ -35,9 +34,6 @@ void AssertValidPath(const ResourcePath& path) { } // namespace -DocumentKey::DocumentKey() : path_{std::make_shared()} { -} - DocumentKey::DocumentKey(const ResourcePath& path) : path_{std::make_shared(path)} { AssertValidPath(*path_); @@ -48,46 +44,11 @@ DocumentKey::DocumentKey(ResourcePath&& path) AssertValidPath(*path_); } -DocumentKey DocumentKey::FromPathString(const absl::string_view path) { - return DocumentKey{ResourcePath::FromString(path)}; -} - -DocumentKey DocumentKey::FromSegments(std::initializer_list list) { - return DocumentKey{ResourcePath{list}}; -} - const DocumentKey& DocumentKey::Empty() { - static DocumentKey empty; + static const DocumentKey empty; return empty; } -bool DocumentKey::IsDocumentKey(const ResourcePath& path) { - return path.size() % 2 == 0; -} - -const ResourcePath& DocumentKey::path() const { - return path_ ? *path_ : Empty().path(); -} - -bool operator==(const DocumentKey& lhs, const DocumentKey& rhs) { - return lhs.path() == rhs.path(); -} -bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs) { - return lhs.path() != rhs.path(); -} -bool operator<(const DocumentKey& lhs, const DocumentKey& rhs) { - return lhs.path() < rhs.path(); -} -bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs) { - return lhs.path() <= rhs.path(); -} -bool operator>(const DocumentKey& lhs, const DocumentKey& rhs) { - return lhs.path() > rhs.path(); -} -bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs) { - return lhs.path() >= rhs.path(); -} - } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h index 3c9f809a6fe..0c7ce976ce8 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.h +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -21,6 +21,7 @@ #include #include +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "absl/strings/string_view.h" namespace firebase { @@ -35,7 +36,8 @@ class ResourcePath; class DocumentKey { public: /** Creates a "blank" document key not associated with any document. */ - DocumentKey(); + DocumentKey() : path_{std::make_shared()} { + } /** Creates a new document key containing a copy of the given path. */ explicit DocumentKey(const ResourcePath& path); @@ -47,18 +49,27 @@ class DocumentKey { * Creates and returns a new document key using '/' to split the string into * segments. */ - static DocumentKey FromPathString(absl::string_view path); + static DocumentKey FromPathString(const absl::string_view path) { + return DocumentKey{ResourcePath::FromString(path)}; + } /** Creates and returns a new document key with the given segments. */ - static DocumentKey FromSegments(std::initializer_list list); + static DocumentKey FromSegments(std::initializer_list list) { + return DocumentKey{ResourcePath{list}}; + } /** Returns a shared instance of an empty document key. */ static const DocumentKey& Empty(); /** Returns true iff the given path is a path to a document. */ - static bool IsDocumentKey(const ResourcePath& path); + static bool IsDocumentKey(const ResourcePath& path) { + return path.size() % 2 == 0; + } + /** The path to the document. */ - const ResourcePath& path() const; + const ResourcePath& path() const { + return path_ ? *path_ : Empty().path(); + } private: // This is an optimization to make passing DocumentKey around cheaper (it's @@ -66,12 +77,24 @@ class DocumentKey { std::shared_ptr path_; }; -bool operator==(const DocumentKey& lhs, const DocumentKey& rhs); -bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs); -bool operator<(const DocumentKey& lhs, const DocumentKey& rhs); -bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs); -bool operator>(const DocumentKey& lhs, const DocumentKey& rhs); -bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs); +inline bool operator==(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() == rhs.path(); +} +inline bool operator!=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() != rhs.path(); +} +inline bool operator<(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() < rhs.path(); +} +inline bool operator<=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() <= rhs.path(); +} +inline bool operator>(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() > rhs.path(); +} +inline bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs) { + return lhs.path() >= rhs.path(); +} } // namespace model } // namespace firestore From 03163e543736df7d13b085ccb45441827bf160b8 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 9 Feb 2018 16:49:04 -0500 Subject: [PATCH 45/48] add new files to cmakelists --- Firestore/core/src/firebase/firestore/model/CMakeLists.txt | 2 ++ Firestore/core/test/firebase/firestore/model/CMakeLists.txt | 1 + 2 files changed, 3 insertions(+) diff --git a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt index 169e3101f70..95310ec9bde 100644 --- a/Firestore/core/src/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/src/firebase/firestore/model/CMakeLists.txt @@ -18,6 +18,8 @@ cc_library( base_path.h database_id.cc database_id.h + document_key.cc + document_key.h field_path.cc field_path.h field_value.cc diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt index 282befde2e1..e73be9805fe 100644 --- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt +++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt @@ -16,6 +16,7 @@ cc_test( firebase_firestore_model_test SOURCES database_id_test.cc + document_key_test.cc field_path_test.cc field_value_test.cc snapshot_version_test.cc From 0bd98980d58bb5d33d6a994718cbd0bec87c5a30 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 9 Feb 2018 16:54:27 -0500 Subject: [PATCH 46/48] remove leftover forward declaration --- Firestore/core/src/firebase/firestore/model/document_key.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h index 0c7ce976ce8..6eb1e181745 100644 --- a/Firestore/core/src/firebase/firestore/model/document_key.h +++ b/Firestore/core/src/firebase/firestore/model/document_key.h @@ -28,8 +28,6 @@ namespace firebase { namespace firestore { namespace model { -class ResourcePath; - /** * DocumentKey represents the location of a document in the Firestore database. */ From d5074943434f0e93cb8e1c29e797b9262fd4b76c Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 9 Feb 2018 18:02:42 -0500 Subject: [PATCH 47/48] fix style --- .../core/test/firebase/firestore/model/document_key_test.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc index 22c21c3e715..6cd24f51475 100644 --- a/Firestore/core/test/firebase/firestore/model/document_key_test.cc +++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc @@ -14,14 +14,13 @@ * limitations under the License. */ -#include "Firestore/core/src/firebase/firestore/model/document_key.h" -#include "Firestore/core/src/firebase/firestore/model/resource_path.h" - #include #include #include #include +#include "Firestore/core/src/firebase/firestore/model/document_key.h" +#include "Firestore/core/src/firebase/firestore/model/resource_path.h" #include "gtest/gtest.h" namespace firebase { From 6583dccaf46ba5452c81c214cc551e05b5b6d986 Mon Sep 17 00:00:00 2001 From: Konstantin Varlamov Date: Fri, 9 Feb 2018 18:54:40 -0500 Subject: [PATCH 48/48] assert_death -> assert_any_throw; fix incorrect expectation that creating from empty string should throw --- .../firestore/model/document_key_test.cc | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc index 6cd24f51475..0e0df2dab9d 100644 --- a/Firestore/core/test/firebase/firestore/model/document_key_test.cc +++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc @@ -87,21 +87,21 @@ TEST(DocumentKey, Constructor_StaticFactory) { EXPECT_EQ(path_string, key_from_string.path().CanonicalString()); EXPECT_EQ(path_string, key_from_segments.path().CanonicalString()); EXPECT_EQ(key_from_segments, key_from_string); + + const auto from_empty_path = DocumentKey::FromPathString(""); + EXPECT_EQ(from_empty_path, DocumentKey{}); } TEST(DocumentKey, Constructor_BadArguments) { - ASSERT_DEATH_IF_SUPPORTED(DocumentKey(ResourcePath{"foo"}), ""); - ASSERT_DEATH_IF_SUPPORTED(DocumentKey(ResourcePath{"foo", "bar", "baz"}), ""); - - ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromSegments({"foo"}), ""); - ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromSegments({"foo", "bar", "baz"}), - ""); - - ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString(""), ""); - ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid"), ""); - ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid//string"), ""); - ASSERT_DEATH_IF_SUPPORTED(DocumentKey::FromPathString("invalid/key/path"), - ""); + ASSERT_ANY_THROW(DocumentKey(ResourcePath{"foo"})); + ASSERT_ANY_THROW(DocumentKey(ResourcePath{"foo", "bar", "baz"})); + + ASSERT_ANY_THROW(DocumentKey::FromSegments({"foo"})); + ASSERT_ANY_THROW(DocumentKey::FromSegments({"foo", "bar", "baz"})); + + ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid")); + ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid//string")); + ASSERT_ANY_THROW(DocumentKey::FromPathString("invalid/key/path")); } TEST(DocumentKey, IsDocumentKey) {