diff --git a/string_algorithms.cpp b/string_algorithms.cpp new file mode 100644 index 0000000..c6881b4 --- /dev/null +++ b/string_algorithms.cpp @@ -0,0 +1,98 @@ +static_assert( __cplusplus > 2020'00 ); + +#include "string_algorithms.h" + +#include +#include + +#include "error.hpp" + +namespace Alepha::Cavorite ::detail:: string_algorithms +{ + namespace + { + namespace C + { + const bool debug= false; + const bool debugExpansion= false or C::debug; + const bool debugCommas= false or C::debug; + } + } + + std::string + exports::expandVariables( const std::string &text, const VarMap &vars, const char sigil ) + { + if( C::debugExpansion ) error() << "Expanding variables in " << text << std::endl; + + std::string rv; + std::string varName; + + enum { Symbol, Normal } mode= Normal; + + for( const char ch: text ) + { + if( mode == normal and ch == sigil ) + { + mode= Symbol; + varName.clear(); + continue; + } + if( mode == Symbol and ch == sigil ) + { + mode= Normal; + if( not varName.empty() ) + { + if( not vars.contains( varName ) ) + { + throw std::runtime_error( "No such variable: `" + varName + "`" ); + } + if( C::debugVariableExpansion ) error() << "Expanding variable with name `" << varName << "`" << std::endl; + rv+= vars.at( varName )(); + } + else rv+= sigil; + continue; + } + + auto ¤t= mode == Normal ? rv : varName; + current+= ch; + } + + if( mode != Normal ) throw std::runtime_error( "Unterminated variable `" + varName + " in expansion of `" + text + "`" ); + if( C::debugExpansion ) error() << "Expansion was: `" << rv << "`" << std::endl; + + return rv; + } + + std::vector< std::string > + exports::parseCommas( const std::string &string ) + { + enum { Text, Backslash } state= Text; + + std::vector< std::string > rv; + std::string next; + for( const char ch: string ) + { + if( state == Backslash ) + { + state= Text; + next+= ch; + } + else if( state == Text ) + { + if( ch == '\\' ) state= Backslash; + else if( ch == ',' ) + { + if( C::debugCommas ) error() << "Parsed from commas: `" << next << "`" << std::endl; + rv.push_back( std::move( next ) ); + next.clear(); + } + else next+= ch; + } + } + + if( C::debugCommas ) error() << "Final parsed from commas: `" << next << "`" << std::endl; + rv.push_back( std::move( next ) ); + + return rv; + } +} diff --git a/string_algorithms.h b/string_algorithms.h new file mode 100644 index 0000000..3d86162 --- /dev/null +++ b/string_algorithms.h @@ -0,0 +1,71 @@ +static_assert( __cplusplus > 2020'00 ); + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace Alepha::inline Cavorite ::detail:: string_algorithms +{ + inline namespace exports {} + + using VarMap= std::map< std::string, std::function< std::string () > >; + + inline namespace exports + { + /*! + * Returns a new string with text-replacement variables expanded. + * + * @param text The source text to expand variables within. + * @param vars A map of variable names to values to expand. + * @param sigil A character which encloses the variable name. (If the character is `'%'` for example, + * then `"%variable%"` is a variable name.) + */ + std::string expandVariables( const std::string &text, const VarMap &vars, const char sigil ); + + /*! + * Returns a vector of strings parsed from a comma separated string. + * + * @note This function is almost a split function, but it supports explicit + * backslashes for various special characters and the `','` character. + * + * @param text The text to parse. + * @return A vector of the substrings found in the string. + */ + std::vector< std::string > parseCommas( const std::string &text ); + + + /*! + * Parses an integral range description into a vector of values. + */ + template< Integral T > + std::vector< T > + parseRange( const std::string &s ) + { + auto tokens= split( s, "-" ); + if( tokens.empty() or tokens.size() > 2 ) + { + throw std::runtime_error( "Expected an integer or a range." ); + } + // If there's no range, or we had a negative number, just emit that. + if( tokens.size() == 1 or tokens.at( 0 ).empty() ) return { boost::lexical_cast< T >( s ) }; + + const auto low= boost::lexical_cast< T >( tokens.at( 0 ) ); + const auto high= boost::lexical_cast< T >( tokens.at( 0 ) ); + + std::vector< T > rv{ high - low + 1 }; + std::iota( begin( rv ), end( rv ), low ); + + return rv; + } +} + +namespace Alepha::Cavorite::inline exports::inline string_algorithms +{ + using namespace detail::exports::string_algorithms; +}