changeset 30:a76e55e098d1

util: rewrite functions in C-ish there are C++ bindings still put in place. the code should be valid C, except for the use of <regex>, which ought to go anyway. eventually I'll actually *test* this stuff aside from the TrimRight crap
author Paper <paper@tflc.us>
date Sun, 09 Feb 2025 23:18:57 -0500
parents 40fd3776ce9b
children 668f4f31ddda
files include/animone/util.h src/util.cc
diffstat 2 files changed, 193 insertions(+), 76 deletions(-) [+]
line wrap: on
line diff
--- a/include/animone/util.h	Sun Feb 09 23:15:55 2025 -0500
+++ b/include/animone/util.h	Sun Feb 09 23:18:57 2025 -0500
@@ -1,25 +1,89 @@
 #ifndef ANIMONE_ANIMONE_UTIL_H_
 #define ANIMONE_ANIMONE_UTIL_H_
 
-#include <sstream>
-#include <string>
+#include <stdint.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
 
-namespace animone::internal::util {
+int animone_internal_util_ReadFile(const char *path, char **data, size_t *size);
+int animone_internal_util_EqualStrings(const char *str1, const char *str2);
+char *animone_internal_util_Stem(const char *filename);
+int animone_internal_util_CheckPattern(const char *pattern, const char *str);
+int animone_internal_util_TrimLeft(char *str, const char* chars);
+int animone_internal_util_TrimRight(char *str, const char* chars);
+
+intmax_t animone_internal_util_StringToInt(const char *ptr, intmax_t def);
+
+#ifdef __cplusplus
+}
 
-bool ReadFile(const std::string& path, std::string& data);
-bool EqualStrings(const std::string& str1, const std::string& str2);
-bool Stem(const std::string& filename, std::string& stem);
-bool CheckPattern(const std::string& pattern, const std::string& str);
-bool TrimLeft(std::string& str, const char* chars);
-bool TrimRight(std::string& str, const char* chars);
+#include <string>
+#include <cstdlib>
+
+namespace animone {
+namespace internal {
+namespace util {
 
-template<typename T = int, std::enable_if_t<std::is_integral<T>::value, bool> = true>
-T StringToInt(const std::string& str, T def = 0) {
-	std::istringstream s(str);
-	s >> std::noboolalpha >> def;
-	return def;
+// Compatibility bindings, to keep the existing C++ code working
+inline bool ReadFile(const std::string &path, std::string &data)
+{
+	char *data_c;
+	size_t size_c;
+
+	int x = ::animone_internal_util_ReadFile(path.c_str(), &data_c, &size_c);
+
+	data.assign(data_c, size_c);
+
+	std::free(data_c);
+
+	return static_cast<bool>(x);
 }
 
-} // namespace animone::internal::util
+inline bool EqualStrings(const std::string &str1, const std::string &str2)
+{
+	return static_cast<bool>(::animone_internal_util_EqualStrings(str1.c_str(), str2.c_str()));
+}
+
+inline bool Stem(const std::string &filename, std::string &stem)
+{
+	char *stem_c = ::animone_internal_util_Stem(filename.c_str());
+	if (!stem_c)
+		return 0;
+
+	stem.assign(stem_c);
+
+	std::free(stem_c);
+
+	return 1;
+}
+
+inline bool CheckPattern(const std::string &pattern, const std::string &str)
+{
+	return static_cast<bool>(::animone_internal_util_CheckPattern(pattern.c_str(), str.c_str()));
+}
+
+inline bool TrimLeft(std::string &str, const char *chars)
+{
+	return ::animone_internal_util_TrimLeft(str.data(), chars);
+}
+
+inline bool TrimRight(std::string &str, const char* chars)
+{
+	return ::animone_internal_util_TrimRight(str.data(), chars);
+}
+
+inline intmax_t StringToInt(const std::string &ptr, intmax_t def)
+{
+	return ::animone_internal_util_StringToInt(ptr.c_str(), def);
+}
+
+} // namespace util
+} // namespace internal
+} // namespace animone
+
+#endif
 
 #endif // ANIMONE_ANIMONE_UTIL_H_
--- a/src/util.cc	Sun Feb 09 23:15:55 2025 -0500
+++ b/src/util.cc	Sun Feb 09 23:18:57 2025 -0500
@@ -1,85 +1,138 @@
-#include <algorithm>
-#include <fstream>
-#include <regex>
-#include <sstream>
-#include <string>
-
 #include "animone/util.h"
 
-namespace animone::internal::util {
+#include <string.h>
+
+#include <regex> // FIXME use a C library for this
 
-bool ReadFile(const std::string& path, std::string& data) {
-	std::ifstream file(path.c_str(), std::ios::in | std::ios::binary);
-	if (!file)
-		return false;
+int animone_internal_util_ReadFile(const char *path, char **data, size_t *size)
+{
+	// whatever
+	size_t sz_local;
+	if (!size)
+		size = &sz_local;
+
+	FILE *f = fopen(path, "r");
+	if (!f)
+		return 0;
 
-	std::ostringstream string;
-	string << file.rdbuf();
-	file.close();
+	fseek(f, 0, SEEK_END);
+	long end = ftell(f);
+	if (end < 0)
+		return 0;
+	fseek(f, 0, SEEK_SET);
+
+	*size = end;
 
-	data = string.str();
+	if (data) {
+		// add a NUL terminator anyway
+		*data = (char *)malloc(end + 1);
+		if (!*data) {
+			*size = 0;
+			return 0;
+		}
 
-	return true;
+		*size = fread(*data, 1, *size, f);
+		(*data)[*size] = '\0';
+	}
+
+	return 1;
 }
 
-/* this assumes ASCII... which really should be the case for what we need, anyway */
-bool EqualStrings(const std::string& str1, const std::string& str2) {
-	auto tolower = [](const char c) -> char { return ('A' <= c && c <= 'Z') ? c + ('a' - 'A') : c; };
-
-	auto equal_chars = [&tolower](const char c1, const char c2) -> bool { return tolower(c1) == tolower(c2); };
-
-	return str1.length() == str2.length() && std::equal(str1.begin(), str1.end(), str2.begin(), equal_chars);
+static inline unsigned char _animone_internal_tolower(unsigned char c)
+{
+	return (c >= 'A' && c <= 'Z') ? (c | 0x20) : c;
 }
 
-bool Stem(const std::string& filename, std::string& stem) {
-	unsigned long long pos = filename.find_last_of(".");
-	if (pos != std::string::npos)
-		return false;
+/* this is, in essence, strcasecmp. */
+int animone_internal_util_EqualStrings(const char *str1, const char *str2)
+{
+	unsigned char c1, c2;
 
-	stem = filename.substr(0, pos);
-	return true;
+	do {
+		c1 = _animone_internal_tolower(*str1++);
+		c2 = _animone_internal_tolower(*str2++);
+	} while (c1 == c2 && c1);
+
+	return !!(c1 - c2);
 }
 
-bool CheckPattern(const std::string& pattern, const std::string& str) {
-	if (pattern.empty())
-		return false;
-	if (pattern.front() == '^' && std::regex_match(str, std::regex(pattern)))
-		return true;
-	return util::EqualStrings(pattern, str);
+char *animone_internal_Stem(const char *filename)
+{
+	const char *ptr = strrchr(filename, '.');
+	if (!ptr)
+		return NULL;
+
+	size_t len = ptr - filename;
+
+	char *stem = (char *)malloc(len + 1);
+	if (!stem)
+		return NULL;
+
+	memcpy(stem, filename, len);
+	stem[len] = '\0';
+
+	return stem;
+}
+
+int animone_internal_util_CheckPattern(const char *pattern, const char *str)
+{
+	switch (*pattern) {
+	case '\0': return 0;
+	case '^':
+		// FIXME: Convert to C
+		if (std::regex_match(std::string(str), std::regex(pattern)))
+			return 1;
+	default:
+		break;
+	}
+
+	return animone_internal_util_EqualStrings(pattern, str);
 }
 
-bool TrimLeft(std::string& str, const char* chars) {
-	if (str.empty())
-		return false;
-
-	const auto found = str.find_first_not_of(chars);
+/* Modifies `str` inplace */
+int animone_internal_util_TrimLeft(char *str, const char* chars)
+{
+	if (!str || !*str)
+		return 0;
 
-	if (found == 0)
-		return false;
+	const size_t found = strcspn(str, chars);
+	const size_t len = strlen(str);
 
-	if (found == std::string::npos)
-		str.clear();
-	else
-		str.erase(0, found);
+	if (found == len)
+		return 1; // nothing to do
 
-	return true;
+	memmove(str, str + found, len - found);
+	str[len - found] = '\0';
+
+	return 1;
 }
 
-bool TrimRight(std::string& str, const char* chars) {
-	if (str.empty())
-		return false;
-
-	const auto found = str.find_last_not_of(chars);
+// reverse version of strcspn
+static inline size_t _animone_internal_strrcspn(const char *str1, const char *str2)
+{
+	const size_t str1len = strlen(str1);
+	ptrdiff_t i; /* FIXME: this should be using size_t */
 
-	if (found == str.size() - 1)
-		return false;
+	for (i = str1len - 1; i >= 0; i--) {
+		size_t found = strcspn(str1 + i, str2);
+		if (found != str1len - i)
+            return i;
+	}
 
-	if (found == std::string::npos)
-		str.clear();
-	else
-		str.resize(found + 1);
-
-	return true;
+    return str1len;
 }
 
-} // namespace animone::internal::util
+int animone_internal_util_TrimRight(char *str, const char* chars)
+{
+	if (!str || !*str)
+		return 0;
+
+	// We can get away without really moving memory around here.
+	// The old version simply used std::string::find_last_not_of,
+	// which I'm fairly sure isn't the intention of this function
+	// (for example find_last_not_of("gb    ") returns "gb   ").
+	const size_t found = _animone_internal_strrcspn(str, chars);
+	str[found] = '\0';
+
+	return 1;
+}