[ox] Cleanup string len handling
Some checks failed
Build / build (push) Has been cancelled

Remove UTF-8 parsing. It is a rare enough need that it should have a specialized call when needed.
Better to have a more optimal length fetch for typical case.
This commit is contained in:
Gary Talent 2024-05-10 01:29:13 -05:00
parent bec75d2eba
commit 043df533b7
15 changed files with 97 additions and 136 deletions

View File

@ -44,7 +44,7 @@ bool ClArgs::getBool(ox::CRStringView arg, bool defaultValue) const noexcept {
String ClArgs::getString(ox::CRStringView arg, const char *defaultValue) const noexcept { String ClArgs::getString(ox::CRStringView arg, const char *defaultValue) const noexcept {
auto [value, err] = m_strings.at(arg); auto [value, err] = m_strings.at(arg);
return !err ? ox::String(std::move(*value)) : ox::String(defaultValue); return !err ? ox::String(*value) : ox::String(defaultValue);
} }
int ClArgs::getInt(ox::CRStringView arg, int defaultValue) const noexcept { int ClArgs::getInt(ox::CRStringView arg, int defaultValue) const noexcept {

View File

@ -159,7 +159,7 @@ Error Directory<FileStore, InodeId_t>::mkdir(PathIterator path, bool parents, Fi
// determine if already exists // determine if already exists
auto name = nameBuff; auto name = nameBuff;
oxReturnError(path.get(name)); oxReturnError(path.get(*name));
auto childInode = find(PathIterator(*name)); auto childInode = find(PathIterator(*name));
if (!childInode.ok()) { if (!childInode.ok()) {
// if this is not the last item in the path and parents is disabled, // if this is not the last item in the path and parents is disabled,
@ -203,7 +203,7 @@ Error Directory<FileStore, InodeId_t>::write(PathIterator path, uint64_t inode64
auto name = nameBuff; auto name = nameBuff;
if (path.next().hasNext()) { // not yet at target directory, recurse to next one if (path.next().hasNext()) { // not yet at target directory, recurse to next one
oxReturnError(path.get(name)); oxReturnError(path.get(*name));
oxTracef("ox.fs.Directory.write", "Attempting to write to next sub-Directory: {} of {}", oxTracef("ox.fs.Directory.write", "Attempting to write to next sub-Directory: {} of {}",
*name, path.fullPath()); *name, path.fullPath());
oxRequire(nextChild, findEntry(*name)); oxRequire(nextChild, findEntry(*name));
@ -222,7 +222,7 @@ Error Directory<FileStore, InodeId_t>::write(PathIterator path, uint64_t inode64
// insert the new entry on this directory // insert the new entry on this directory
// get the name // get the name
oxReturnError(path.next(name)); oxReturnError(path.next(*name));
// find existing version of directory // find existing version of directory
oxTracef("ox.fs.Directory.write", "Searching for directory inode {}", m_inodeId); oxTracef("ox.fs.Directory.write", "Searching for directory inode {}", m_inodeId);
@ -263,7 +263,7 @@ Error Directory<FileStore, InodeId_t>::remove(PathIterator path, FileName *nameB
nameBuff = new (ox_alloca(sizeof(FileName))) FileName; nameBuff = new (ox_alloca(sizeof(FileName))) FileName;
} }
auto &name = *nameBuff; auto &name = *nameBuff;
oxReturnError(path.get(&name)); oxReturnError(path.get(name));
oxTrace("ox.fs.Directory.remove", name); oxTrace("ox.fs.Directory.remove", name);
auto buff = m_fs.read(m_inodeId).template to<Buffer>(); auto buff = m_fs.read(m_inodeId).template to<Buffer>();
@ -343,7 +343,7 @@ Result<typename FileStore::InodeId_t> Directory<FileStore, InodeId_t>::find(Path
// determine if already exists // determine if already exists
auto name = nameBuff; auto name = nameBuff;
oxReturnError(path.get(name)); oxReturnError(path.get(*name));
oxRequire(v, findEntry(*name)); oxRequire(v, findEntry(*name));
// recurse if not at end of path // recurse if not at end of path

View File

@ -61,8 +61,9 @@ Error PathIterator::fileName(char *out, std::size_t outSize) {
} }
// Gets the get item in the path // Gets the get item in the path
Error PathIterator::get(char *pathOut, std::size_t pathOutSize) { Error PathIterator::get(IString<MaxFileNameLength> &fileName) {
std::size_t size = 0; std::size_t size = 0;
std::ignore = fileName.resize(MaxFileNameLength);
if (m_iterator >= m_maxSize) { if (m_iterator >= m_maxSize) {
oxTracef("ox.fs.PathIterator.get", "m_iterator ({}) >= m_maxSize ({})", m_iterator, m_maxSize); oxTracef("ox.fs.PathIterator.get", "m_iterator ({}) >= m_maxSize ({})", m_iterator, m_maxSize);
return OxError(1); return OxError(1);
@ -84,22 +85,25 @@ Error PathIterator::get(char *pathOut, std::size_t pathOutSize) {
const auto end = static_cast<size_t>(substr - m_path); const auto end = static_cast<size_t>(substr - m_path);
size = end - start; size = end - start;
// cannot fit the output in the output parameter // cannot fit the output in the output parameter
if (size >= pathOutSize || size == 0) { if (size >= MaxFileNameLength || size == 0) {
return OxError(1); return OxError(1);
} }
ox::memcpy(pathOut, &m_path[start], size); ox::memcpy(fileName.data(), &m_path[start], size);
// truncate trailing / // truncate trailing /
if (size && pathOut[size - 1] == '/') { if (size && fileName[size - 1] == '/') {
size--; size--;
} }
pathOut[size] = 0; // end with null terminator oxReturnError(fileName.resize(size));
return OxError(0); return {};
} }
// Gets the get item in the path /**
Error PathIterator::next(char *pathOut, std::size_t pathOutSize) { * @return 0 if no error
*/
Error PathIterator::next(IString<MaxFileNameLength> &fileName) {
std::size_t size = 0; std::size_t size = 0;
auto retval = OxError(1); auto retval = OxError(1);
std::ignore = fileName.resize(MaxFileNameLength);
if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) { if (m_iterator < m_maxSize && ox::strlen(&m_path[m_iterator])) {
retval = OxError(0); retval = OxError(0);
if (m_path[m_iterator] == '/') { if (m_path[m_iterator] == '/') {
@ -115,34 +119,21 @@ Error PathIterator::next(char *pathOut, std::size_t pathOutSize) {
const auto end = static_cast<size_t>(substr - m_path); const auto end = static_cast<size_t>(substr - m_path);
size = end - start; size = end - start;
// cannot fit the output in the output parameter // cannot fit the output in the output parameter
if (size >= pathOutSize) { if (size >= MaxFileNameLength) {
return OxError(1); return OxError(1);
} }
ox::memcpy(pathOut, &m_path[start], size); ox::memcpy(fileName.data(), &m_path[start], size);
} }
// truncate trailing / // truncate trailing /
if (size && pathOut[size - 1] == '/') { if (size && fileName[size - 1] == '/') {
size--; size--;
} }
pathOut[size] = 0; // end with null terminator fileName[size] = 0; // end with null terminator
oxReturnError(fileName.resize(size));
m_iterator += size; m_iterator += size;
return retval; return retval;
} }
/**
* @return 0 if no error
*/
Error PathIterator::get(IString<MaxFileNameLength> *fileName) {
return get(fileName->data(), fileName->cap());
}
/**
* @return 0 if no error
*/
Error PathIterator::next(IString<MaxFileNameLength> *fileName) {
return next(fileName->data(), fileName->cap());
}
Result<std::size_t> PathIterator::nextSize() const { Result<std::size_t> PathIterator::nextSize() const {
std::size_t size = 0; std::size_t size = 0;
auto retval = OxError(1); auto retval = OxError(1);

View File

@ -41,22 +41,12 @@ class PathIterator {
/** /**
* @return 0 if no error * @return 0 if no error
*/ */
Error next(char *pathOut, std::size_t pathOutSize); Error next(FileName &fileName);
/** /**
* @return 0 if no error * @return 0 if no error
*/ */
Error get(char *pathOut, std::size_t pathOutSize); Error get(FileName &fileName);
/**
* @return 0 if no error
*/
Error next(FileName *fileName);
/**
* @return 0 if no error
*/
Error get(FileName *fileName);
/** /**
* @return 0 if no error * @return 0 if no error

View File

@ -60,10 +60,10 @@ const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests =
[](ox::StringView) { [](ox::StringView) {
auto const path = ox::String("/usr/share/charset.gbag"); auto const path = ox::String("/usr/share/charset.gbag");
ox::PathIterator it(path.c_str(), path.len()); ox::PathIterator it(path.c_str(), path.len());
auto buff = static_cast<char*>(ox_alloca(path.len() + 1)); ox::FileName buff;
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && buff == "usr", "PathIterator shows wrong next");
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && buff == "share", "PathIterator shows wrong next");
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "charset.gbag") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && buff == "charset.gbag", "PathIterator shows wrong next");
return OxError(0); return OxError(0);
} }
}, },
@ -72,9 +72,9 @@ const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests =
[](ox::StringView) { [](ox::StringView) {
auto const path = ox::String("/usr/share/"); auto const path = ox::String("/usr/share/");
ox::PathIterator it(path.c_str(), path.len()); ox::PathIterator it(path.c_str(), path.len());
auto buff = static_cast<char*>(ox_alloca(path.len() + 1)); ox::FileName buff;
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next");
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next");
return OxError(0); return OxError(0);
} }
}, },
@ -83,8 +83,8 @@ const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests =
[](ox::StringView) { [](ox::StringView) {
auto const path = ox::String("/"); auto const path = ox::String("/");
ox::PathIterator it(path.c_str(), path.len()); ox::PathIterator it(path.c_str(), path.len());
auto buff = static_cast<char*>(ox_alloca(path.len() + 1)); ox::FileName buff;
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "\0") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "\0") == 0, "PathIterator shows wrong next");
return OxError(0); return OxError(0);
} }
}, },
@ -93,10 +93,10 @@ const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests =
[](ox::StringView) { [](ox::StringView) {
auto const path = ox::String("usr/share/charset.gbag"); auto const path = ox::String("usr/share/charset.gbag");
ox::PathIterator it(path.c_str(), path.len()); ox::PathIterator it(path.c_str(), path.len());
auto buff = static_cast<char*>(ox_alloca(path.len() + 1)); ox::FileName buff;
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next");
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next");
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "charset.gbag") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "charset.gbag") == 0, "PathIterator shows wrong next");
return OxError(0); return OxError(0);
} }
}, },
@ -105,9 +105,9 @@ const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests =
[](ox::StringView) { [](ox::StringView) {
auto const path = ox::String("usr/share/"); auto const path = ox::String("usr/share/");
ox::PathIterator it(path.c_str(), path.len()); ox::PathIterator it(path.c_str(), path.len());
auto buff = static_cast<char*>(ox_alloca(path.len() + 1)); ox::FileName buff;
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "usr") == 0, "PathIterator shows wrong next");
oxAssert(it.next(buff, path.len()) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next"); oxAssert(it.next(buff) == 0 && ox::strcmp(buff, "share") == 0, "PathIterator shows wrong next");
return OxError(0); return OxError(0);
} }
}, },
@ -237,8 +237,9 @@ const std::map<ox::StringView, std::function<ox::Error(ox::StringView)>> tests =
}; };
int main(int argc, const char **args) { int main(int argc, const char **args) {
if (argc < 3) { if (argc < 2) {
oxError("Must specify test to run and test argument"); oxError("Must specify test to run");
return -1;
} }
ox::StringView const testName = args[1]; ox::StringView const testName = args[1];
ox::StringView const testArg = args[2] ? args[2] : nullptr; ox::StringView const testArg = args[2] ? args[2] : nullptr;

View File

@ -322,9 +322,6 @@ constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, BasicString<
*val = BasicString<SmallStringSize>(cap); *val = BasicString<SmallStringSize>(cap);
auto data = val->data(); auto data = val->data();
// read the string // read the string
if (static_cast<StringLength>(cap) < size) {
return OxError(McOutputBuffEnded);
}
oxReturnError(m_reader.read(data, size)); oxReturnError(m_reader.read(data, size));
} else { } else {
*val = ""; *val = "";
@ -336,8 +333,23 @@ constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, BasicString<
template<Reader_c Reader> template<Reader_c Reader>
template<std::size_t L> template<std::size_t L>
constexpr Error MetalClawReaderTemplate<Reader>::field(const char *name, IString<L> *val) noexcept { constexpr Error MetalClawReaderTemplate<Reader>::field(const char*, IString<L> *val) noexcept {
return fieldCString(name, val->data(), val->cap()); if (!m_unionIdx.has_value() || static_cast<std::size_t>(*m_unionIdx) == m_field) {
if (m_fieldPresence.get(static_cast<std::size_t>(m_field))) {
// read the length
std::size_t bytesRead = 0;
oxRequire(size, mc::decodeInteger<StringLength>(m_reader, &bytesRead));
*val = IString<L>();
oxReturnError(val->resize(size));
auto const data = val->data();
// read the string
oxReturnError(m_reader.read(data, size));
} else {
*val = "";
}
}
++m_field;
return {};
} }
template<Reader_c Reader> template<Reader_c Reader>

View File

@ -157,7 +157,8 @@ std::map<ox::StringView, ox::Error(*)()> tests = {
oxAssert(testIn.Int8 == testOut.Int8, "Int8 value mismatch"); oxAssert(testIn.Int8 == testOut.Int8, "Int8 value mismatch");
oxAssert(testIn.Union.Int == testOut.Union.Int, "Union.Int value mismatch"); oxAssert(testIn.Union.Int == testOut.Union.Int, "Union.Int value mismatch");
oxAssert(testIn.String == testOut.String, "String value mismatch"); oxAssert(testIn.String == testOut.String, "String value mismatch");
oxAssert(testIn.IString == testOut.IString, "IString value mismatch"); oxDebugf("{}", testOut.IString.len());
oxExpect(testIn.IString, testOut.IString);
oxAssert(testIn.List[0] == testOut.List[0], "List[0] value mismatch"); oxAssert(testIn.List[0] == testOut.List[0], "List[0] value mismatch");
oxAssert(testIn.List[1] == testOut.List[1], "List[1] value mismatch"); oxAssert(testIn.List[1] == testOut.List[1], "List[1] value mismatch");
oxAssert(testIn.List[2] == testOut.List[2], "List[2] value mismatch"); oxAssert(testIn.List[2] == testOut.List[2], "List[2] value mismatch");

View File

@ -207,7 +207,7 @@ constexpr Error MetalClawWriter<Writer>::field(const char*, const BasicString<Sm
template<Writer_c Writer> template<Writer_c Writer>
template<std::size_t L> template<std::size_t L>
constexpr Error MetalClawWriter<Writer>::field(const char *name, const IString<L> *val) noexcept { constexpr Error MetalClawWriter<Writer>::field(const char *name, const IString<L> *val) noexcept {
return fieldCString(name, val->data(), val->cap()); return fieldCString(name, val->data(), val->len());
} }
template<Writer_c Writer> template<Writer_c Writer>

View File

@ -207,7 +207,19 @@ Error OrganicClawReader::field(const char *key, BasicString<L> *val) noexcept {
template<std::size_t L> template<std::size_t L>
Error OrganicClawReader::field(const char *key, IString<L> *val) noexcept { Error OrganicClawReader::field(const char *key, IString<L> *val) noexcept {
return fieldCString(key, val->data(), val->cap()); auto err = OxError(0);
if (targetValid()) {
const auto &jv = value(key);
if (jv.empty()) {
*val = IString<L>{};
} else if (jv.isString()) {
*val = jv.asString().c_str();
} else {
err = OxError(1, "Type mismatch");
}
}
++m_fieldIt;
return err;
} }
// array handler // array handler

View File

@ -45,7 +45,7 @@ add_library(
if(NOT MSVC) if(NOT MSVC)
target_compile_options(OxStd PRIVATE -Wsign-conversion) target_compile_options(OxStd PRIVATE -Wsign-conversion)
target_compile_options(OxStd PRIVATE -Wconversion) target_compile_options(OxStd PRIVATE -Wconversion)
if(${OX_OS_LINUX}) if(${OX_OS_LINUX} AND NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(OxStd PUBLIC -export-dynamic) target_compile_options(OxStd PUBLIC -export-dynamic)
#target_link_options(OxStd PUBLIC -W1,-E) #target_link_options(OxStd PUBLIC -W1,-E)
elseif(${OX_OS_FREEBSD}) elseif(${OX_OS_FREEBSD})

View File

@ -85,7 +85,9 @@ class IString {
* Returns the capacity of bytes for this string. * Returns the capacity of bytes for this string.
*/ */
[[nodiscard]] [[nodiscard]]
constexpr std::size_t cap() const noexcept; constexpr static std::size_t cap() noexcept {
return StrCap;
}
}; };
template<std::size_t size> template<std::size_t size>
@ -113,7 +115,7 @@ constexpr IString<size> &IString<size>::operator=(Integer_c auto i) noexcept {
template<std::size_t size> template<std::size_t size>
constexpr IString<size> &IString<size>::operator=(ox::CRStringView str) noexcept { constexpr IString<size> &IString<size>::operator=(ox::CRStringView str) noexcept {
std::size_t strLen = str.bytes() + 1; std::size_t strLen = str.len();
if (cap() < strLen) { if (cap() < strLen) {
strLen = cap(); strLen = cap();
} }
@ -126,14 +128,7 @@ constexpr IString<size> &IString<size>::operator=(ox::CRStringView str) noexcept
template<std::size_t size> template<std::size_t size>
constexpr IString<size> &IString<size>::operator=(const char *str) noexcept { constexpr IString<size> &IString<size>::operator=(const char *str) noexcept {
std::size_t strLen = ox::strlen(str) + 1; operator=(ox::StringView{str});
if (cap() < strLen) {
strLen = cap();
}
m_size = strLen;
ox::listcpy(m_buff.data(), str, strLen);
// make sure last element is a null terminator
m_buff[cap()] = 0;
return *this; return *this;
} }
@ -170,30 +165,21 @@ constexpr char &IString<StrCap>::operator[](std::size_t i) noexcept {
template<std::size_t StrCap> template<std::size_t StrCap>
constexpr Error IString<StrCap>::append(const char *str, std::size_t strLen) noexcept { constexpr Error IString<StrCap>::append(const char *str, std::size_t strLen) noexcept {
Error err{}; Error err{};
auto currentLen = len(); auto const currentLen = len();
if (cap() < currentLen + strLen + 1) { if (cap() < currentLen + strLen) {
strLen = cap() - currentLen; strLen = cap() - currentLen;
err = OxError(1, "Insufficient space for full string"); err = OxError(1, "Insufficient space for full string");
} }
ox::strncpy(m_buff.data() + currentLen, str, strLen); ox::strncpy(m_buff.data() + currentLen, str, strLen);
// make sure last element is a null terminator // make sure last element is a null terminator
m_buff[currentLen + strLen] = 0; m_buff[currentLen + strLen] = 0;
m_size += strLen;
return err; return err;
} }
template<std::size_t StrCap> template<std::size_t StrCap>
constexpr Error IString<StrCap>::append(ox::StringView str) noexcept { constexpr Error IString<StrCap>::append(ox::StringView str) noexcept {
auto strLen = str.len(); return append(str.data(), str.len());
Error err{};
auto currentLen = len();
if (cap() < currentLen + strLen + 1) {
strLen = cap() - currentLen;
err = OxError(1, "Insufficient space for full string");
}
ox::strncpy(m_buff.data() + currentLen, str, strLen);
// make sure last element is a null terminator
m_buff[currentLen + strLen] = 0;
return err;
} }
template<std::size_t StrCap> template<std::size_t StrCap>
@ -214,27 +200,12 @@ constexpr const char *IString<StrCap>::c_str() const noexcept {
template<std::size_t StrCap> template<std::size_t StrCap>
constexpr std::size_t IString<StrCap>::len() const noexcept { constexpr std::size_t IString<StrCap>::len() const noexcept {
std::size_t length = 0; return m_size;
for (std::size_t i = 0; i < StrCap; i++) {
auto const b = static_cast<uint8_t>(m_buff[i]);
if (b) {
const auto asciiChar = (b & 128) == 0;
const auto utf8Char = (b & (256 << 6)) == (256 << 6);
if (asciiChar || utf8Char) {
length++;
}
} else {
break;
}
}
return length;
} }
template<std::size_t StrCap> template<std::size_t StrCap>
constexpr std::size_t IString<StrCap>::bytes() const noexcept { constexpr std::size_t IString<StrCap>::bytes() const noexcept {
std::size_t i = 0; return m_size + 1;
for (i = 0; i < StrCap && m_buff[i]; i++);
return i + 1; // add one for null terminator
} }
template<std::size_t StrCap> template<std::size_t StrCap>
@ -249,11 +220,6 @@ constexpr ox::Error IString<StrCap>::resize(size_t sz) noexcept {
return {}; return {};
} }
template<std::size_t StrCap>
constexpr std::size_t IString<StrCap>::cap() const noexcept {
return StrCap;
}
template<size_t sz> template<size_t sz>
struct MaybeView<ox::IString<sz>> { struct MaybeView<ox::IString<sz>> {
using type = ox::StringView; using type = ox::StringView;

View File

@ -33,7 +33,7 @@ template<typename T1, typename T2>
constexpr T1 *listcpy(T1 *dest, T2 *src, std::size_t maxLen) noexcept { constexpr T1 *listcpy(T1 *dest, T2 *src, std::size_t maxLen) noexcept {
using T1Type = typename ox::remove_reference<decltype(dest[0])>::type; using T1Type = typename ox::remove_reference<decltype(dest[0])>::type;
std::size_t i = 0; std::size_t i = 0;
while (i < maxLen && src[i]) { while (i < maxLen) {
dest[i] = static_cast<T1Type>(src[i]); dest[i] = static_cast<T1Type>(src[i]);
++i; ++i;
} }

View File

@ -515,7 +515,7 @@ constexpr char &BasicString<SmallStringSize_v>::operator[](std::size_t i) noexce
template<std::size_t SmallStringSize_v> template<std::size_t SmallStringSize_v>
constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::substr(std::size_t pos) const noexcept { constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::substr(std::size_t pos) const noexcept {
return BasicString(m_buff.data() + pos, m_buff.size() - pos); return BasicString(m_buff.data() + pos, m_buff.size() - pos - 1);
} }
template<std::size_t SmallStringSize_v> template<std::size_t SmallStringSize_v>
@ -531,26 +531,12 @@ constexpr BasicString<SmallStringSize_v> BasicString<SmallStringSize_v>::substr(
template<std::size_t SmallStringSize_v> template<std::size_t SmallStringSize_v>
constexpr std::size_t BasicString<SmallStringSize_v>::bytes() const noexcept { constexpr std::size_t BasicString<SmallStringSize_v>::bytes() const noexcept {
std::size_t i; return m_buff.size();
for (i = 0; i < m_buff.size() && m_buff[i]; ++i);
return i + 1; // add one for null terminator
} }
template<std::size_t SmallStringSize_v> template<std::size_t SmallStringSize_v>
constexpr std::size_t BasicString<SmallStringSize_v>::len() const noexcept { constexpr std::size_t BasicString<SmallStringSize_v>::len() const noexcept {
std::size_t length = 0; return m_buff.size() - 1;
for (const auto c : m_buff) {
const auto b = static_cast<uint8_t>(c);
if (b) {
// normal ASCII character or start of UTF-8 character
if ((b & 128) == 0 || (b & (256 << 6)) == (256 << 6)) {
++length;
}
} else {
break;
}
}
return length;
} }
template<std::size_t SmallStringSize_v> template<std::size_t SmallStringSize_v>

View File

@ -109,6 +109,7 @@ static std::map<ox::StringView, ox::Error(*)()> tests = {
oxAssert(ox::StringView("Write") != ox::String(""), "String / StringView comparison broken"); oxAssert(ox::StringView("Write") != ox::String(""), "String / StringView comparison broken");
oxAssert(ox::String("Write") != ox::StringView(""), "String / StringView comparison broken"); oxAssert(ox::String("Write") != ox::StringView(""), "String / StringView comparison broken");
oxAssert(ox::String("Write") == ox::StringView("Write"), "String / StringView comparison broken"); oxAssert(ox::String("Write") == ox::StringView("Write"), "String / StringView comparison broken");
oxExpect(ox::String("asdf").substr(1, 3), "sd");
oxAssert( oxAssert(
ox::String(ox::StringView("Write")) == ox::StringView("Write"), ox::String(ox::StringView("Write")) == ox::StringView("Write"),
"String / StringView comparison broken"); "String / StringView comparison broken");

View File

@ -176,9 +176,10 @@ class UUID {
[[nodiscard]] [[nodiscard]]
constexpr UUIDStr toString() const noexcept { constexpr UUIDStr toString() const noexcept {
UUIDStr out; UUIDStr out;
ox::CharBuffWriter bw(out.data(), out.cap()); std::ignore = out.resize(UUIDStr::cap());
ox::CharBuffWriter bw(out.data(), UUIDStr::cap());
std::ignore = toString(bw); std::ignore = toString(bw);
out[out.cap()] = 0; out[UUIDStr::cap()] = 0;
return out; return out;
} }
}; };