[wpiutil] Change StringExtras split() to template (#7636)

It now calls back a function for each part rather than creating a SmallVector.
This commit is contained in:
Peter Johnson
2025-01-05 20:53:43 -08:00
committed by GitHub
parent 0f6693594c
commit 03d9e96877
17 changed files with 161 additions and 171 deletions

View File

@@ -102,7 +102,8 @@ static void RebuildEntryTree() {
} else {
parts.emplace_back(prefix);
}
wpi::split(mainpart, parts, '/', -1, false);
wpi::split(mainpart, '/', -1, false,
[&](auto part) { parts.emplace_back(part); });
// ignore a raw "/" key
if (parts.empty()) {

View File

@@ -1015,7 +1015,8 @@ void NetworkTablesModel::RebuildTreeImpl(std::vector<TreeNode>* tree,
continue;
}
parts.clear();
wpi::split(entry->info.name, parts, '/', -1, false);
wpi::split(entry->info.name, '/', -1, false,
[&](auto part) { parts.emplace_back(part); });
// ignore a raw "/" key
if (parts.empty()) {

View File

@@ -68,7 +68,8 @@ void NetworkTablesProvider::DisplayMenu() {
wpi::SmallString<64> name;
for (auto&& entry : m_viewEntries) {
path.clear();
wpi::split(entry->name, path, '/', -1, false);
wpi::split(entry->name, '/', -1, false,
[&](auto name) { path.emplace_back(name); });
bool fullDepth = true;
int depth = 0;

View File

@@ -12,7 +12,6 @@
#include <imgui.h>
#include <imgui_stdlib.h>
#include <ntcore_cpp.h>
#include <wpi/SmallVector.h>
#include <wpi/StringExtras.h>
#include "glass/Context.h"
@@ -69,12 +68,10 @@ void NetworkTablesSettings::Thread::Main() {
(team = wpi::parse_integer<unsigned int>(serverTeam, 10))) {
nt::SetServerTeam(m_inst, team.value(), m_port);
} else {
wpi::SmallVector<std::string_view, 4> serverNames;
std::vector<std::pair<std::string_view, unsigned int>> servers;
wpi::split(serverTeam, serverNames, ',', -1, false);
for (auto&& serverName : serverNames) {
wpi::split(serverTeam, ',', -1, false, [&](auto serverName) {
servers.emplace_back(serverName, m_port);
}
});
nt::SetServer(m_inst, servers);
}

View File

@@ -352,16 +352,15 @@ void InitializeTeamNumber(void) {
std::string_view hostname{hostnameBuf, sizeof(hostnameBuf)};
// hostname is frc-{TEAM}-roborio
// Split string around '-' (max of 2 splits), take the second element of the
// resulting array.
wpi::SmallVector<std::string_view> elements;
wpi::split(hostname, elements, "-", 2);
if (elements.size() < 3) {
teamNumber = 0;
return;
}
teamNumber = wpi::parse_integer<int32_t>(elements[1], 10).value_or(0);
// Split string around '-' (max of 2 splits), take the second element
teamNumber = 0;
int i = 0;
wpi::split(hostname, '-', 2, false, [&](auto part) {
if (i == 1) {
teamNumber = wpi::parse_integer<int32_t>(part, 10).value_or(0);
}
++i;
});
}
int32_t HAL_GetTeamNumber(void) {

View File

@@ -245,7 +245,8 @@ void SerialHelper::QueryHubPaths(int32_t* status) {
wpi::SmallVector<std::string_view, 16> pathSplitVec;
// Split path into individual directories
wpi::split(path, pathSplitVec, '/', -1, false);
wpi::split(path, '/', -1, false,
[&](auto part) { pathSplitVec.emplace_back(part); });
// Find each individual item index
int findusb = -1;

View File

@@ -10,7 +10,6 @@
#include <utility>
#include <vector>
#include <wpi/SmallVector.h>
#include <wpi/StringExtras.h>
#include <wpi/fs.h>
#include <wpi/print.h>
@@ -100,7 +99,6 @@ int HAL_LoadOneExtension(const char* library) {
int HAL_LoadExtensions(void) {
int rc = 1;
wpi::SmallVector<std::string_view, 2> libraries;
const char* e = std::getenv("HALSIM_EXTENSIONS");
if (!e) {
if (GetShowNotFoundMessage()) {
@@ -109,13 +107,11 @@ int HAL_LoadExtensions(void) {
}
return rc;
}
wpi::split(e, libraries, DELIM, -1, false);
for (auto& library : libraries) {
rc = HAL_LoadOneExtension(std::string(library).c_str());
if (rc < 0) {
break;
wpi::split(e, DELIM, -1, false, [&](auto library) {
if (rc >= 0) {
rc = HAL_LoadOneExtension(std::string(library).c_str());
}
}
});
return rc;
}

View File

@@ -18,7 +18,6 @@
#include <utility>
#include <wpi/MemoryBuffer.h>
#include <wpi/SmallString.h>
#include <wpi/StringExtras.h>
#include <wpi/fs.h>
#include <wpi/mutex.h>
@@ -233,16 +232,15 @@ void InitializeTeamNumber(void) {
std::string_view hostname{hostnameBuf, sizeof(hostnameBuf)};
// hostname is frc-{TEAM}-roborio
// Split string around '-' (max of 2 splits), take the second element of the
// resulting array.
wpi::SmallVector<std::string_view> elements;
wpi::split(hostname, elements, "-", 2);
if (elements.size() < 3) {
teamNumber = 0;
return;
}
teamNumber = wpi::parse_integer<int32_t>(elements[1], 10).value_or(0);
// Split string around '-' (max of 2 splits), take the second element
teamNumber = 0;
int i = 0;
wpi::split(hostname, '-', 2, false, [&](auto part) {
if (i == 1) {
teamNumber = wpi::parse_integer<int32_t>(part, 10).value_or(0);
}
++i;
});
}
int32_t HAL_GetTeamNumber(void) {

View File

@@ -54,12 +54,10 @@ std::string_view NetworkTable::NormalizeKey(std::string_view key,
buf.push_back(PATH_SEPARATOR_CHAR);
}
// for each path element, add it with a slash following
wpi::SmallVector<std::string_view, 16> parts;
wpi::split(key, parts, PATH_SEPARATOR_CHAR, -1, false);
for (auto i = parts.begin(); i != parts.end(); ++i) {
buf.append(i->begin(), i->end());
wpi::split(key, PATH_SEPARATOR_CHAR, -1, false, [&](auto part) {
buf.append(part.begin(), part.end());
buf.push_back(PATH_SEPARATOR_CHAR);
}
});
// remove trailing slash if the input key didn't have one
if (!key.empty() && key.back() != PATH_SEPARATOR_CHAR) {
buf.pop_back();
@@ -72,19 +70,17 @@ std::vector<std::string> NetworkTable::GetHierarchy(std::string_view key) {
hierarchy.emplace_back(1, PATH_SEPARATOR_CHAR);
// for each path element, add it to the end of what we built previously
wpi::SmallString<128> path;
wpi::SmallVector<std::string_view, 16> parts;
wpi::split(key, parts, PATH_SEPARATOR_CHAR, -1, false);
if (!parts.empty()) {
for (auto i = parts.begin(); i != parts.end(); ++i) {
path += PATH_SEPARATOR_CHAR;
path += *i;
hierarchy.emplace_back(path.str());
}
// handle trailing slash
if (key.back() == PATH_SEPARATOR_CHAR) {
path += PATH_SEPARATOR_CHAR;
hierarchy.emplace_back(path.str());
}
bool any = false;
wpi::split(key, PATH_SEPARATOR_CHAR, -1, false, [&](auto part) {
any = true;
path += PATH_SEPARATOR_CHAR;
path += part;
hierarchy.emplace_back(path.str());
});
// handle trailing slash
if (any && key.back() == PATH_SEPARATOR_CHAR) {
path += PATH_SEPARATOR_CHAR;
hierarchy.emplace_back(path.str());
}
return hierarchy;
}

View File

@@ -9,7 +9,6 @@
#include <string>
#include <wpi/SmallString.h>
#include <wpi/SmallVector.h>
#include <wpi/StringExtras.h>
#include <wpi/print.h>
#include <wpinet/uv/util.h>
@@ -73,15 +72,8 @@ bool HALSimWS::Initialize() {
const char* msgFilters = std::getenv("HALSIMWS_FILTERS");
if (msgFilters != nullptr) {
m_useMsgFiltering = true;
std::string_view filters(msgFilters);
filters = wpi::trim(filters);
wpi::SmallVector<std::string_view, 16> filtersSplit;
wpi::split(filters, filtersSplit, ',', -1, false);
for (auto val : filtersSplit) {
m_msgFilters[wpi::trim(val)] = true;
}
wpi::split(wpi::trim(msgFilters), ',', -1, false,
[&](auto val) { m_msgFilters[wpi::trim(val)] = true; });
} else {
m_useMsgFiltering = false;
}

View File

@@ -83,15 +83,8 @@ bool HALSimWeb::Initialize() {
const char* msgFilters = std::getenv("HALSIMWS_FILTERS");
if (msgFilters != nullptr) {
m_useMsgFiltering = true;
std::string_view filters(msgFilters);
filters = wpi::trim(filters);
wpi::SmallVector<std::string_view, 16> filtersSplit;
wpi::split(filters, filtersSplit, ',', -1, false);
for (auto val : filtersSplit) {
m_msgFilters[wpi::trim(val)] = true;
}
wpi::split(wpi::trim(msgFilters), ',', -1, false,
[&](auto val) { m_msgFilters[wpi::trim(val)] = true; });
} else {
m_useMsgFiltering = false;
}

View File

@@ -128,7 +128,8 @@ void LogLoader::RebuildEntryTree() {
} else {
parts.emplace_back(prefix);
}
wpi::split(mainpart, parts, '/', -1, false);
wpi::split(mainpart, '/', -1, false,
[&](auto part) { parts.emplace_back(part); });
// ignore a raw "/" key
if (parts.empty()) {

View File

@@ -98,9 +98,7 @@ std::string_view EscapeHTML(std::string_view str, SmallVectorImpl<char>& buf) {
}
HttpQueryMap::HttpQueryMap(std::string_view query) {
SmallVector<std::string_view, 16> queryElems;
split(query, queryElems, '&', 100, false);
for (auto elem : queryElems) {
split(query, '&', 100, false, [&](auto elem) {
auto [nameEsc, valueEsc] = split(elem, '=');
SmallString<64> nameBuf;
bool err = false;
@@ -109,7 +107,7 @@ HttpQueryMap::HttpQueryMap(std::string_view query) {
if (!err) {
m_elems.try_emplace(name, valueEsc);
}
}
});
}
std::optional<std::string_view> HttpQueryMap::Get(
@@ -132,9 +130,7 @@ HttpPath::HttpPath(std::string_view path) {
m_pathEnds.emplace_back(0);
return;
}
wpi::SmallVector<std::string_view, 16> pathElems;
split(path, pathElems, '/', 100, false);
for (auto elem : pathElems) {
split(path, '/', 100, false, [&](auto elem) {
SmallString<64> buf;
bool err = false;
auto val = wpi::UnescapeURI(elem, buf, &err);
@@ -144,7 +140,7 @@ HttpPath::HttpPath(std::string_view path) {
}
m_pathBuf += val;
m_pathEnds.emplace_back(m_pathBuf.size());
}
});
}
bool HttpPath::startswith(size_t start,

View File

@@ -5,6 +5,7 @@
#include "wpinet/WebSocketServer.h"
#include <memory>
#include <string>
#include <utility>
#include <wpi/StringExtras.h>
@@ -31,14 +32,12 @@ WebSocketServerHelper::WebSocketServerHelper(HttpParser& req) {
m_version = value;
} else if (equals_lower(name, "sec-websocket-protocol")) {
// Protocols are comma delimited, repeated headers add to list
SmallVector<std::string_view, 2> protocols;
split(value, protocols, ",", -1, false);
for (auto protocol : protocols) {
split(value, ',', -1, false, [&](auto protocol) {
protocol = trim(protocol);
if (!protocol.empty()) {
m_protocols.emplace_back(protocol);
}
}
});
}
});
req.headersComplete.connect([&req, this](bool) {
@@ -53,7 +52,22 @@ std::pair<bool, std::string_view> WebSocketServerHelper::MatchProtocol(
if (protocols.empty() && m_protocols.empty()) {
return {true, {}};
}
for (auto protocol : protocols) {
for (auto&& protocol : protocols) {
for (auto&& clientProto : m_protocols) {
if (protocol == clientProto) {
return {true, protocol};
}
}
}
return {false, {}};
}
std::pair<bool, std::string_view> WebSocketServerHelper::MatchProtocol(
std::span<const std::string> protocols) {
if (protocols.empty() && m_protocols.empty()) {
return {true, {}};
}
for (auto&& protocol : protocols) {
for (auto&& clientProto : m_protocols) {
if (protocol == clientProto) {
return {true, protocol};
@@ -101,9 +115,7 @@ WebSocketServer::WebSocketServer(uv::Stream& stream,
}
// Negotiate sub-protocol
SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
m_protocols.end()};
std::string_view protocol = m_helper.MatchProtocol(protocols).second;
std::string_view protocol = m_helper.MatchProtocol(m_protocols).second;
// Disconnect our header reader
m_dataConn.disconnect();

View File

@@ -56,6 +56,18 @@ class WebSocketServerHelper {
std::pair<bool, std::string_view> MatchProtocol(
std::span<const std::string_view> protocols);
/**
* Try to find a match to the list of sub-protocols provided by the client.
* The list is priority ordered, so the first match wins.
* Only valid during and after the upgrade event.
* @param protocols Acceptable protocols
* @return Pair; first item is true if a match was made, false if not.
* Second item is the matched protocol if a match was made, otherwise
* is empty.
*/
std::pair<bool, std::string_view> MatchProtocol(
std::span<const std::string> protocols);
/**
* Try to find a match to the list of sub-protocols provided by the client.
* The list is priority ordered, so the first match wins.

View File

@@ -22,7 +22,6 @@
#include <string_view>
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
// strncasecmp() is not available on non-POSIX systems, so define an
// alternative function here.
@@ -118,65 +117,6 @@ bool wpi::ends_with_lower(std::string_view str,
suffix.data(), suffix.size()) == 0;
}
void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
std::string_view separator, int maxSplit,
bool keepEmpty) noexcept {
std::string_view s = str;
// Count down from maxSplit. When maxSplit is -1, this will just split
// "forever". This doesn't support splitting more than 2^31 times
// intentionally; if we ever want that we can make maxSplit a 64-bit integer
// but that seems unlikely to be useful.
while (maxSplit-- != 0) {
auto idx = s.find(separator);
if (idx == std::string_view::npos) {
break;
}
// Push this split.
if (keepEmpty || idx > 0) {
arr.push_back(slice(s, 0, idx));
}
// Jump forward.
s = slice(s, idx + separator.size(), std::string_view::npos);
}
// Push the tail.
if (keepEmpty || !s.empty()) {
arr.push_back(s);
}
}
void wpi::split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
char separator, int maxSplit, bool keepEmpty) noexcept {
std::string_view s = str;
// Count down from maxSplit. When maxSplit is -1, this will just split
// "forever". This doesn't support splitting more than 2^31 times
// intentionally; if we ever want that we can make maxSplit a 64-bit integer
// but that seems unlikely to be useful.
while (maxSplit-- != 0) {
size_t idx = s.find(separator);
if (idx == std::string_view::npos) {
break;
}
// Push this split.
if (keepEmpty || idx > 0) {
arr.push_back(slice(s, 0, idx));
}
// Jump forward.
s = slice(s, idx + 1, std::string_view::npos);
}
// Push the tail.
if (keepEmpty || !s.empty()) {
arr.push_back(s);
}
}
static unsigned GetAutoSenseRadix(std::string_view& str) noexcept {
if (str.empty()) {
return 10;

View File

@@ -17,6 +17,7 @@
#pragma once
#include <concepts>
#include <iterator>
#include <limits>
#include <optional>
@@ -541,42 +542,95 @@ constexpr std::pair<std::string_view, std::string_view> rsplit(
/**
* Splits @p str into substrings around the occurrences of a separator string.
*
* Each substring is stored in @p arr. If @p maxSplit is >= 0, at most
* @p maxSplit splits are done and consequently <= @p maxSplit + 1
* elements are added to arr.
* If @p keepEmpty is false, empty strings are not added to @p arr. They
* still count when considering @p maxSplit
* Each substring is passed to the callback @p func. If @p maxSplit is >= 0, at
* most @p maxSplit splits are done and consequently <= @p maxSplit + 1
* elements are provided to the callback.
* If @p keepEmpty is false, empty strings are not included. They
* still count when considering @p maxSplit.
* An useful invariant is that
* separator.join(arr) == str if maxSplit == -1 and keepEmpty == true
* separator.join(all callbacks) == str if keepEmpty == true.
*
* @param arr Where to put the substrings.
* @param separator The string to split on.
* @param maxSplit The maximum number of times the string is split.
* @param keepEmpty True if empty substring should be added.
* @param func Function to call for each substring.
*/
void split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
std::string_view separator, int maxSplit = -1,
bool keepEmpty = true) noexcept;
template <std::invocable<std::string_view> F>
void split(std::string_view str, std::string_view separator, int maxSplit,
bool keepEmpty, F&& func) {
std::string_view s = str;
// Count down from maxSplit. When maxSplit is -1, this will just split
// "forever". This doesn't support splitting more than 2^31 times
// intentionally; if we ever want that we can make maxSplit a 64-bit integer
// but that seems unlikely to be useful.
while (maxSplit-- != 0) {
auto idx = s.find(separator);
if (idx == std::string_view::npos) {
break;
}
// Provide this split.
if (keepEmpty || idx > 0) {
func(slice(s, 0, idx));
}
// Jump forward.
s = slice(s, idx + separator.size(), std::string_view::npos);
}
// Provide the tail.
if (keepEmpty || !s.empty()) {
func(s);
}
}
/**
* Splits @p str into substrings around the occurrences of a separator
* character.
*
* Each substring is stored in @p arr. If @p maxSplit is >= 0, at most
* @p maxSplit splits are done and consequently <= @p maxSplit + 1
* elements are added to arr.
* If @p keepEmpty is false, empty strings are not added to @p arr. They
* still count when considering @p maxSplit
* Each substring is passed to the callback @p func. If @p maxSplit is >= 0, at
* most @p maxSplit splits are done and consequently <= @p maxSplit + 1
* elements are provided to the callback.
* If @p keepEmpty is false, empty strings are not included. They
* still count when considering @p maxSplit.
* An useful invariant is that
* separator.join(arr) == str if maxSplit == -1 and keepEmpty == true
* separator.join(all callbacks) == str if keepEmpty == true.
*
* @param arr Where to put the substrings.
* @param separator The character to split on.
* @param maxSplit The maximum number of times the string is split.
* @param keepEmpty True if empty substring should be added.
* @param func Function to call for each substring.
*/
void split(std::string_view str, SmallVectorImpl<std::string_view>& arr,
char separator, int maxSplit = -1, bool keepEmpty = true) noexcept;
template <std::invocable<std::string_view> F>
void split(std::string_view str, char separator, int maxSplit, bool keepEmpty,
F&& func) {
std::string_view s = str;
// Count down from maxSplit. When maxSplit is -1, this will just split
// "forever". This doesn't support splitting more than 2^31 times
// intentionally; if we ever want that we can make maxSplit a 64-bit integer
// but that seems unlikely to be useful.
while (maxSplit-- != 0) {
size_t idx = s.find(separator);
if (idx == std::string_view::npos) {
break;
}
// Provide this split.
if (keepEmpty || idx > 0) {
func(slice(s, 0, idx));
}
// Jump forward.
s = slice(s, idx + 1, std::string_view::npos);
}
// Provide the tail.
if (keepEmpty || !s.empty()) {
func(s);
}
}
/**
* Returns @p str with consecutive @p ch characters starting from the