diff --git a/string_algorithms.cpp b/string_algorithms.cpp index 4fc147a..299ecba 100644 --- a/string_algorithms.cpp +++ b/string_algorithms.cpp @@ -2,6 +2,7 @@ static_assert( __cplusplus > 2020'00 ); #include "string_algorithms.h" +#include #include #include @@ -17,11 +18,136 @@ namespace Alepha::Cavorite ::detail:: string_algorithms const bool debugExpansion= false or C::debug; const bool debugCommas= false or C::debug; } + + struct VariableExpansionStreambuf + : public std::streambuf + { + public: + std::streambuf *underlying= nullptr; + VarMap substitutions; + std::stringbuf varName; + char sigil; + enum { Symbol, Normal } mode= Normal; + + void + writeChar( const char ch ) + { + std::ostream current{ underlying }; + + if( mode == Normal and ch == sigil ) + { + mode= Symbol; + varName.str( "" ); + return; + } + if( mode == Symbol and ch == sigil ) + { + mode= Normal; + const std::string name( varName.view() ); + if( not name.empty() ) + { + if( not substitutions.contains( name ) ) + { + 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( mode == Symbol ) current.rdbuf( &varName ); + current << ch; + } + + int + overflow( const int ch ) override + { + if( ch == EOF ) throw std::logic_error{ "EOF!" }; + writeChar( ch ); + + return 1; + } + + std::streamsize + xsputn( const char *const data, const std::streamsize amt ) override + { + for( std::streamsize i= 0; i< amt; ++i ) overflow( data[ i ] ); + return amt; + } + + void + drain() + { + if( mode != Normal ) throw std::runtime_error{ "Unterminated variable `" + varName.str() + " in expansion." }; + } + }; + + const auto wrapperIndex= std::ios::xalloc(); + + void + releaseWrapper( std::ios_base &ios ) + { + auto *const streambuf= static_cast< VariableExpansionStreambuf * >( ios.pword( wrapperIndex ) ); + if( not streambuf ) throw std::logic_error{ "Attempt to remove a substitution context which doesn't exist." }; + + streambuf->drain(); + dynamic_cast< std::ostream & >( ios ).rdbuf( streambuf->underlying ); + delete streambuf; + ios.pword( wrapperIndex )= nullptr; + } + + void + wordwrapCallback( const std::ios_base::event event, std::ios_base &ios, const int idx ) + { + if( wrapperIndex != idx ) throw std::logic_error{ "Wrong index." }; + + if( not ios.pword( wrapperIndex ) ) return; + + if( event == std::ios_base::erase_event ) releaseWrapper( ios ); + } + } + + std::ostream & + impl::operator << ( std::ostream &os, StartSubstitutions &¶ms ) + { + auto streambuf= std::make_unique< VariableExpansionStreambuf >(); + streambuf->underlying= os.rdbuf( streambuf.get() ); + streambuf->substitutions= std::move( params.substitutions ); + streambuf->sigil= params.sigil; + auto &state= os.iword( wrapperIndex ); + if( not state ) + { + state= 1; + os.register_callback( wordwrapCallback, wrapperIndex ); + } + + assert( os.pword( wrapperIndex ) == nullptr ); + os.pword( wrapperIndex )= streambuf.release(); + + return os; + } + + std::ostream & + impl::operator << ( std::ostream &os, EndSubstitutions_t ) + { + releaseWrapper( os ); + return os; } std::string exports::expandVariables( const std::string &text, const VarMap &vars, const char sigil ) { + #if 1 + std::ostringstream oss; + + oss << StartSubstitutions{ sigil, vars }; + oss << text; + oss << EndSubstitutions; + + return std::move( oss ).str(); + #else if( C::debugExpansion ) error() << "Expanding variables in " << text << std::endl; std::string rv; @@ -61,6 +187,7 @@ namespace Alepha::Cavorite ::detail:: string_algorithms if( C::debugExpansion ) error() << "Expansion was: `" << rv << "`" << std::endl; return rv; + #endif } std::vector< std::string > diff --git a/string_algorithms.h b/string_algorithms.h index efd2852..4ac44b1 100644 --- a/string_algorithms.h +++ b/string_algorithms.h @@ -36,6 +36,28 @@ namespace Alepha::inline Cavorite ::detail:: string_algorithms */ std::string expandVariables( const std::string &text, const VarMap &vars, const char sigil ); + struct StartSubstitutions + { + const char sigil; + VarMap substitutions; + + explicit + StartSubstitutions( const char sigil, VarMap substitutions ) + : sigil( sigil ), substitutions( std::move( substitutions ) ) + {} + }; + + // Note that these function as a stack -- so `EndSubstitutions` will + // terminate the top of that stack. + constexpr struct EndSubstitutions_t {} EndSubstitutions; + + inline namespace impl + { + std::ostream &operator << ( std::ostream &, StartSubstitutions && ); + + std::ostream &operator << ( std::ostream &, EndSubstitutions_t ); + } + /*! * Returns a vector of strings parsed from a comma separated string. *