static_assert( __cplusplus > 2020'99 ); #include "string_algorithms.h" #include #include #include #include #include "error.h" #include "AutoRAII.h" namespace Alepha::Hydrogen ::detail:: string_algorithms_m { namespace { namespace C { const bool debug= false; const bool debugExpansion= false or C::debug; const bool debugCommas= false or C::debug; const bool debugIOStreamLifecycle= false or C::debug; } struct VariableExpansionStreambuf : public IOStreams::StackableStreambuf { public: VarMap substitutions; std::stringbuf varName; char sigil; enum { Symbol= 1, Normal= 0 } mode= Normal; int throws= 0; explicit VariableExpansionStreambuf( std::ostream &os, VarMap &&substitutions, const char sigil ) : StackableStreambuf( os ), substitutions( std::move( substitutions ) ), sigil( sigil ) {} void writeChar( const char ch ) override { if( C::debugExpansion ) error() << "Entry to write and got `" << ch << '`' << std::endl; std::ostream current{ out().rdbuf() }; if( mode == Normal and ch == sigil ) { if( C::debugExpansion ) error() << "Normal and got `" << ch << '`' << std::endl; mode= Symbol; varName.str( "" ); return; } if( mode == Symbol and ch == sigil ) { if( C::debugExpansion ) error() << "Closed symbol and got `" << ch << '`' << std::endl; mode= Normal; const std::string name( varName.view() ); if( C::debugExpansion ) { error() << "Asked to expand `" << name << '`' << std::endl; } if( not name.empty() ) { if( C::debugExpansion ) error() << "Name wasn't empty" << std::endl; if( not substitutions.contains( name ) ) { if( C::debugExpansion ) error() << "Throwing" << std::endl; throw std::runtime_error{ "No such variable: `" + name +"`" }; } if( C::debugExpansion ) error() << "Expanding variable with name `" << name << "`" << std::endl; current << substitutions.at( name )(); } else current << sigil; return; } if( C::debugExpansion ) error() << "Stashing `" << ch << '`' << std::endl; if( mode == Symbol ) current.rdbuf( &varName ); current << ch; } void drain() { if( C::debugIOStreamLifecycle ) error() << "Drain called, and mode is: " << mode << std::endl; if( mode != Normal ) { if( C::debugIOStreamLifecycle ) error() << "Mode not being normal, we're throwing (" << ++throws << " times now)..." << std::endl; mode= Normal; throw std::runtime_error{ "Unterminated variable `" + varName.str() + "` in expansion." }; } } }; } void impl::build_streambuf( std::ostream &os, StartSubstitutions &¶ms ) { new VariableExpansionStreambuf{ os, std::move( params.substitutions ), params.sigil }; } std::string exports::expandVariables( const std::string &text, const VarMap &vars, const char sigil ) { std::ostringstream oss; IOStreams::EnableExceptions exc{ oss }; oss << StartSubstitutions{ sigil, vars }; oss << text; oss << EndSubstitutions; return std::move( oss ).str(); } 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; } std::vector< std::string > exports::split( const std::string &s, const char token ) { std::vector< std::string > rv; std::string next; for( const char ch: s ) { if( ch != token ) { next+= ch; continue; } rv.push_back( std::move( next ) ); next.clear(); } rv.push_back( std::move( next ) ); return rv; } std::vector< std::string > exports::split( std::string s, const std::string &delim ) { std::vector< std::string > rv; while( true ) { const auto pos= s.find( delim ); rv.push_back( s.substr( 0, pos ) ); if( pos == std::string::npos ) break; s= s.substr( pos + delim.size() ); } return rv; } }