Browse Source

Add support for variant cast, add clang-format

Sam Jaffe 4 years ago
parent
commit
ca3151390a

+ 108 - 0
.clang-format

@@ -0,0 +1,108 @@
+---
+Language:        Cpp
+# BasedOnStyle:  LLVM
+AccessModifierOffset: -2
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: Right
+AlignOperands:   true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: true
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: true
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: false
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:   
+  AfterClass:      false
+  AfterControlStatement: false
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  BeforeCatch:     false
+  BeforeElse:      false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit:     80
+CommentPragmas:  '^ IWYU pragma:'
+CompactNamespaces: true
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat:   false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: false
+ForEachMacros:   
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IncludeCategories: 
+  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
+    Priority:        2
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+  - Regex:           '.*'
+    Priority:        1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: false
+IndentWidth:     2
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: true
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 60
+PointerAlignment: Middle
+ReflowComments:  true
+SortIncludes:    true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        Cpp11
+TabWidth:        8
+UseTab:          Never
+...
+

+ 2 - 2
include/string_utils/any_of.h

@@ -15,9 +15,9 @@ namespace string_utils {
 bool any_of(std::string const &) { return false; }
 
 template <typename T, typename... Ts>
-bool any_of(std::string const &value, T && in, Ts &&...rest) {
+bool any_of(std::string const & value, T && in, Ts &&... rest) {
   return value == std::forward<T>(in) ||
-      any_of(value, std::forward<Ts>(rest)...);
+         any_of(value, std::forward<Ts>(rest)...);
 }
 
 }

+ 79 - 18
include/string_utils/cast.h

@@ -13,34 +13,63 @@
 #include <type_traits>
 #include <utility>
 
+#if __has_include(<variant>) && __cplusplus > 201402L
+#define STRING_UTIL_CAST_STD_VARIANT
+#include <variant>
+#endif
+
 #include "any_of.h"
 
-#define cast_number_impl(type, func, ...)       \
-bool cast(std::string const &str, type &to) {   \
-  char *rval;                             \
-  to = func(str.c_str(), &rval, ##__VA_ARGS__); \
-  return rval == str.c_str() + str.length();    \
-}
+#define CAST_NUMBER_IMPL(type, func, ...)                                      \
+  inline bool cast(std::string const & str, type & to) {                       \
+    char * rval;                                                               \
+    to = func(str.c_str(), &rval, ##__VA_ARGS__);                              \
+    return rval == str.c_str() + str.length();                                 \
+  }
+
+namespace string_utils { namespace traits {
+template <typename T, typename = void> struct is_stringy : std::false_type {};
+#if defined(STRING_UTIL_CAST_STD_VARIANT)
+
+template <typename> struct is_variant : std::false_type {};
+template <typename... Ts>
+struct is_variant<std::variant<Ts...>> : std::true_type {};
+
+template <typename T>
+struct is_stringy<
+    T, std::enable_if_t<std::is_constructible<T, std::string const &>{} &&
+                        !is_variant<T>{}>> : std::true_type {};
+#else
+template <typename T>
+struct is_stringy<
+    T, std::enable_if_t<std::is_constructible<T, std::string const &>{}>>
+    : std::true_type {};
+#endif
+}}
 
 namespace string_utils {
+template <typename T> std::pair<T, bool> cast(std::string const & str);
 
-template <typename T, typename = std::enable_if_t<std::is_constructible<T, std::string const &>{}>>
-bool cast(std::string const &str, std::string &to) { to = T(str); return true; }
+template <typename T, typename = std::enable_if_t<traits::is_stringy<T>{}>>
+bool cast(std::string const & str, T & to) {
+  to = T(str);
+  return true;
+}
 
-cast_number_impl(long, std::strtol, 10)
-cast_number_impl(long long, std::strtoll, 10)
-cast_number_impl(float, std::strtof)
-cast_number_impl(double, std::strtod)
-cast_number_impl(long double, std::strtold)
+CAST_NUMBER_IMPL(long, std::strtol, 10);
+CAST_NUMBER_IMPL(long long, std::strtoll, 10);
+CAST_NUMBER_IMPL(float, std::strtof);
+CAST_NUMBER_IMPL(double, std::strtod);
+CAST_NUMBER_IMPL(long double, std::strtold);
 
-bool cast(std::string const &str, int &to) {
+inline bool cast(std::string const & str, int & to) {
   long tmp;
   bool rval = cast(str, tmp);
   to = static_cast<int>(tmp);
   return rval && tmp == static_cast<long>(to);
 }
 
-bool cast(std::string const &str, bool &to) {
+inline bool cast(std::string const & str, bool & to) {
   if (any_of(str, "true", "TRUE", "YES", "1")) {
     to = true;
     return true;
@@ -50,13 +79,45 @@ bool cast(std::string const &str, bool &to) {
   }
   return false;
 }
+}
 
-template <typename T>
-auto cast(std::string const &str) {
+#if defined(STRING_UTIL_CAST_STD_VARIANT)
+namespace string_utils::detail {
+template <size_t I, typename... Ts>
+bool _cast(std::string const & str, std::variant<Ts...> & to) {
+  auto [rval, found] = cast<std::tuple_element_t<I, std::tuple<Ts...>>>(str);
+  if (found) { to = std::move(rval); }
+  return found;
+}
+
+// This is needed due to a compiler bug in Clang that fails to properly compile
+// fold-expressions.
+template <size_t I, typename... Ts>
+bool _cast_chain(std::string const & str, std::variant<Ts...> & to) {
+  if constexpr (I == sizeof...(Ts)) {
+    return false;
+  } else {
+    return _cast<I>(str, to) || _cast_chain<I + 1>(str, to);
+  }
+}
+}
+
+namespace string_utils {
+template <typename... Ts>
+bool cast(std::string const & str, std::variant<Ts...> & to) {
+  return detail::_cast_chain<0>(str, to);
+}
+}
+#endif
+
+// This should be placed last in the file
+namespace string_utils {
+template <typename T> std::pair<T, bool> cast(std::string const & str) {
   using string_utils::cast;
   std::pair<T, bool> rval;
   rval.second = cast(str, rval.first);
   return rval;
 }
-
 }
+
+#undef CAST_NUMBER_IMPL

+ 9 - 8
include/string_utils/tokenizer.h

@@ -20,6 +20,7 @@ public:
     char on;
     std::string escaped;
   };
+
 private:
   std::string divider_;
   quote quote_;
@@ -30,14 +31,14 @@ private:
 
 public:
   tokenizer(std::string divider, struct quote quote = {'\0', ""});
-    
-  tokenizer &max_outputs(size_t new_max_outputs);
-  tokenizer &truncate(bool new_truncate_overage);
-  tokenizer &ignore_empty_tokens(bool new_ignore_empty_tokens);
-  tokenizer &escapable(bool new_escapable);
-
-  std::vector<std::string> operator()(std::string const &input) const;
-  
+
+  tokenizer & max_outputs(size_t new_max_outputs);
+  tokenizer & truncate(bool new_truncate_overage);
+  tokenizer & ignore_empty_tokens(bool new_ignore_empty_tokens);
+  tokenizer & escapable(bool new_escapable);
+
+  std::vector<std::string> operator()(std::string const & input) const;
+
 private:
   size_t max_outputs() const;
 };

+ 12 - 15
src/tokenizer.cxx

@@ -11,37 +11,38 @@
 namespace string_utils {
 
 tokenizer::tokenizer(std::string divider, struct quote quote)
-  : divider_(std::move(divider)), quote_(std::move(quote)) {}
+    : divider_(std::move(divider)), quote_(std::move(quote)) {}
 
-tokenizer &tokenizer::max_outputs(size_t new_max_outputs) {
+tokenizer & tokenizer::max_outputs(size_t new_max_outputs) {
   max_outputs_ = new_max_outputs;
   return *this;
 }
 
-tokenizer &tokenizer::truncate(bool new_truncate) {
+tokenizer & tokenizer::truncate(bool new_truncate) {
   truncate_ = new_truncate;
   return *this;
 }
 
-tokenizer &tokenizer::ignore_empty_tokens(bool new_ignore_empty_tokens) {
+tokenizer & tokenizer::ignore_empty_tokens(bool new_ignore_empty_tokens) {
   ignore_empty_tokens_ = new_ignore_empty_tokens;
   return *this;
 }
 
-tokenizer &tokenizer::escapable(bool new_escapable) {
+tokenizer & tokenizer::escapable(bool new_escapable) {
   escapable_ = new_escapable;
   return *this;
 }
 
-static std::size_t countback(std::string const &str, std::size_t p, char c) {
+static std::size_t countback(std::string const & str, std::size_t p, char c) {
   if (p == 0 || str[p - 1] != c) return 0;
   return p - str.find_last_not_of(c, p - 1) - 1;
 }
 
-std::vector<std::string> tokenizer::operator()(std::string const &input) const {
-  auto equals_from = [&input](std::string const &token, std::size_t from) {
+std::vector<std::string>
+tokenizer::operator()(std::string const & input) const {
+  auto equals_from = [&input](std::string const & token, std::size_t from) {
     return token.size() + from < input.size() &&
-        std::strncmp(input.c_str() + from, token.c_str(), token.size()) == 0;
+           std::strncmp(input.c_str() + from, token.c_str(), token.size()) == 0;
   };
   std::vector<std::string> rval;
   std::string buffer;
@@ -67,18 +68,14 @@ std::vector<std::string> tokenizer::operator()(std::string const &input) const {
     } else if (escapable_ && countback(input, pos, '\\') % 2) {
       buffer.back() = input[pos];
     } else if (!in_quote) {
-      if (!ignore_empty_tokens_ || buffer.size()) {
-        rval.emplace_back(buffer);
-      }
+      if (!ignore_empty_tokens_ || buffer.size()) { rval.emplace_back(buffer); }
       from = pos + 1;
       buffer.clear();
     }
   }
   // Due to the special handling rules of the truncate feature, we need
   // to add an additional layer of handling around empty tokens and buffer
-  if (ignore_empty_tokens_ && equals_from(divider_, from)) {
-    ++from;
-  }
+  if (ignore_empty_tokens_ && equals_from(divider_, from)) { ++from; }
   if (rval.size() < max_outputs_) {
     rval.emplace_back(buffer.empty() ? input.substr(from) : buffer);
   }

+ 1 - 1
string-utils.xcodeproj/xcshareddata/xcschemes/string-utils.xcscheme

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1230"
+   LastUpgradeVersion = "1240"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"

+ 17 - 0
test/cast_test.cxx

@@ -66,3 +66,20 @@ TEST(CastBooleanTest, DoesNotCastAllIntegers) {
   auto [_, success] = cast<bool>("2");
   EXPECT_FALSE(success);
 }
+
+MATCHER_P(HoldsAlternative, typearg,
+          std::string("holds a value of type ") + typeid(typearg).name()) {
+  return std::holds_alternative<decltype(typearg)>(arg);
+}
+
+template <typename T> auto HoldsAlternative() { return HoldsAlternative(T()); }
+
+TEST(CastVariantTest, StringVariantWillAlwaysMatchWhenEncountered) {
+  auto [value, success] = cast<std::variant<std::string, int>>("2");
+  EXPECT_THAT(value, HoldsAlternative<std::string>());
+}
+
+TEST(CastVariantTest, EvaluatesTypesLeftToRight) {
+  auto [value, success] = cast<std::variant<int, std::string>>("2");
+  EXPECT_THAT(value, HoldsAlternative<int>());
+}

+ 1 - 2
test/tokenizer_test.cxx

@@ -45,8 +45,7 @@ TEST(TokenizerTest, IgnoresEmptyElementsOnEnd) {
 TEST(TokenizerTest, TruncateDiscardsOverageInsteadOfNotParsingPast) {
   std::string const input = "A.B.C.D";
   std::vector<std::string> const expected{"A", "B", "C"};
-  EXPECT_THAT(tokenizer(".").max_outputs(3).truncate(true)(input),
-              expected);
+  EXPECT_THAT(tokenizer(".").max_outputs(3).truncate(true)(input), expected);
 }
 
 TEST(TokenizerTest, EmptyIsPlacedCorrectlyWhenEnabled) {