Bläddra i källkod

Add string -> number/bool casting functions.

Sam Jaffe 4 år sedan
förälder
incheckning
a7833da97f

+ 23 - 0
include/string_utils/any_of.h

@@ -0,0 +1,23 @@
+//
+//  any_of.h
+//  string-utils
+//
+//  Created by Sam Jaffe on 2/13/21.
+//  Copyright © 2021 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <string>
+
+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) {
+  return value == std::forward<T>(in) ||
+      any_of(value, std::forward<Ts>(rest)...);
+}
+
+}

+ 62 - 0
include/string_utils/cast.h

@@ -0,0 +1,62 @@
+//
+//  cast.h
+//  string-utils
+//
+//  Created by Sam Jaffe on 2/13/21.
+//  Copyright © 2021 Sam Jaffe. All rights reserved.
+//
+
+#pragma once
+
+#include <cstdlib>
+#include <string>
+#include <type_traits>
+#include <utility>
+
+#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();    \
+}
+
+namespace string_utils {
+
+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; }
+
+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) {
+  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) {
+  if (any_of(str, "true", "TRUE", "YES", "1")) {
+    to = true;
+    return true;
+  } else if (any_of(str, "false", "FALSE", "NO", "0")) {
+    to = false;
+    return true;
+  }
+  return false;
+}
+
+template <typename T>
+auto cast(std::string const &str) {
+  using string_utils::cast;
+  std::pair<T, bool> rval;
+  rval.second = cast(str, rval.first);
+  return rval;
+}
+
+}

+ 6 - 2
string-utils.xcodeproj/project.pbxproj

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		CD11D62125D96C620088CB79 /* cast_test.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD11D62025D96C620088CB79 /* cast_test.cxx */; };
 		CD26686B252FF4E800B3E667 /* string_utils in Headers */ = {isa = PBXBuildFile; fileRef = CD26686A252FF4E100B3E667 /* string_utils */; settings = {ATTRIBUTES = (Public, ); }; };
 		CD26687E252FF62F00B3E667 /* tokenizer.cxx in Sources */ = {isa = PBXBuildFile; fileRef = CD26687C252FF62F00B3E667 /* tokenizer.cxx */; };
 		CD26688B252FFAAE00B3E667 /* libstring-utils.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CD266862252FF4B600B3E667 /* libstring-utils.a */; };
@@ -53,6 +54,7 @@
 /* End PBXContainerItemProxy section */
 
 /* Begin PBXFileReference section */
+		CD11D62025D96C620088CB79 /* cast_test.cxx */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = cast_test.cxx; sourceTree = "<group>"; };
 		CD11D62825D96C990088CB79 /* xcode_gtest_helper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xcode_gtest_helper.h; sourceTree = "<group>"; };
 		CD266862252FF4B600B3E667 /* libstring-utils.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libstring-utils.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		CD26686A252FF4E100B3E667 /* string_utils */ = {isa = PBXFileReference; lastKnownFileType = folder; name = string_utils; path = include/string_utils; sourceTree = "<group>"; };
@@ -110,6 +112,7 @@
 			children = (
 				CD11D62825D96C990088CB79 /* xcode_gtest_helper.h */,
 				CD266880252FFA7E00B3E667 /* tokenizer_test.cxx */,
+				CD11D62025D96C620088CB79 /* cast_test.cxx */,
 			);
 			path = test;
 			sourceTree = "<group>";
@@ -294,6 +297,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CD2668B5252FFAD200B3E667 /* tokenizer_test.cxx in Sources */,
+				CD11D62125D96C620088CB79 /* cast_test.cxx in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -314,7 +318,7 @@
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;
@@ -376,7 +380,7 @@
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
-				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LANGUAGE_STANDARD = "c++17";
 				CLANG_CXX_LIBRARY = "libc++";
 				CLANG_ENABLE_MODULES = YES;
 				CLANG_ENABLE_OBJC_ARC = YES;

+ 56 - 0
test/cast_test.cxx

@@ -0,0 +1,56 @@
+//
+//  cast_test.cxx
+//  string_utils-test
+//
+//  Created by Sam Jaffe on 2/14/21.
+//  Copyright © 2021 Sam Jaffe. All rights reserved.
+//
+
+#include "string_utils/cast.h"
+
+#include "xcode_gtest_helper.h"
+
+using testing::Pair;
+using namespace string_utils;
+
+TEST(CastIntegerTest, CastsWithBase10) {
+  auto [value, success] = cast<int>("10");
+  EXPECT_TRUE(success);
+  EXPECT_THAT(value, 10);
+}
+
+TEST(CastIntegerTest, FailsOnBadData) {
+  auto [value, success] = cast<int>("x");
+  EXPECT_FALSE(success);
+  EXPECT_THAT(value, 0);
+}
+
+TEST(CastIntegerTest, FailsOnBadSuffix) {
+  auto [value, success] = cast<int>("10x");
+  EXPECT_FALSE(success);
+  EXPECT_THAT(value, 10);
+}
+
+TEST(CastIntegerTest, FailsOnOutOfBounds) {
+  auto [_, success] = cast<int>("2147483648");
+  EXPECT_FALSE(success);
+}
+
+TEST(CastBooleanTest, CastsVariousTrueValues) {
+  EXPECT_THAT(cast<bool>("true"), Pair(true, true));
+  EXPECT_THAT(cast<bool>("TRUE"), Pair(true, true));
+  EXPECT_THAT(cast<bool>("YES"), Pair(true, true));
+  EXPECT_THAT(cast<bool>("1"), Pair(true, true));
+}
+
+TEST(CastBooleanTest, CastsVariousFalseValues) {
+  EXPECT_THAT(cast<bool>("false"), Pair(false, true));
+  EXPECT_THAT(cast<bool>("FALSE"), Pair(false, true));
+  EXPECT_THAT(cast<bool>("NO"), Pair(false, true));
+  EXPECT_THAT(cast<bool>("0"), Pair(false, true));
+}
+
+TEST(CastBooleanTest, DoesNotCastAllIntegers) {
+  auto [_, success] = cast<bool>("2");
+  EXPECT_FALSE(success);
+}