1
0
forked from Alepha/Alepha

Change variable substitution to work on streams.

This commit is contained in:
2023-10-15 20:05:26 -04:00
parent 3a6a9d01de
commit 3ad9f33953
2 changed files with 149 additions and 0 deletions

View File

@ -2,6 +2,7 @@ static_assert( __cplusplus > 2020'00 );
#include "string_algorithms.h"
#include <memory>
#include <algorithm>
#include <exception>
@ -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 &&params )
{
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 >

View File

@ -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.
*