// SPDX-License-Identifier: MIT
#include "MMessage.hh"
#include <algorithm>
#include <ostream>
#include <vector>
#include "MParameter.hh"
#include "MText.hh"
using namespace std;
const char *const LocaleMatcherName = "InternalLocaleMatches";
namespace
{
const string StreamerPrefix = "InternalStreamerFunctionOf_";
const string BuilderPrefix = "InternalBuilderOf_";
const char *ReturnType( FunctionKind kind )
{
return kind == FunctionKind::String ? "std::string" : "void";
}
const FunctionConcept &ConceptOf( const MessageSettings &settings, FunctionKind kind )
{
switch ( kind )
{
case FunctionKind::String: return settings._String;
case FunctionKind::Error: return settings._Error;
case FunctionKind::Throw: return settings._Throw;
case FunctionKind::Log: break;
}
return settings._Log;
}
constexpr FunctionKind AllKinds[] =
{
FunctionKind::String, FunctionKind::Error, FunctionKind::Throw, FunctionKind::Log
};
}
bool NeedsStreamer( const Message &message )
{
const MessageSettings &settings = *message.Settings;
return settings._Streamable._Active || settings._Error._Active;
}
bool NeedsBuilder( const Message &message )
{
const MessageSettings &settings = *message.Settings;
return settings._String._Active || settings._Log._Active ||
settings._Exception._Active || settings._Throw._Active;
}
void WriteLocaleMatcher( ostream &stream )
{
Mode.OpenHelpers( stream, "LOCALE_MATCHES" );
stream << " // Does the locale name select the message language? An exact match, or" << endl
<< " // the language followed by '_', '.' or '@': \"tr\" and \"tr_TR\" both select" << endl
<< " // \"tr_TR.UTF-8\"; \"C\" selects \"C.UTF-8\"." << endl
<< " [[maybe_unused]] " << Mode.Linkage( ) << "bool " << LocaleMatcherName
<< "( std::string const &name, std::string const &language )" << endl
<< " {" << endl
<< " return name.compare( 0, language.size( ), language ) == 0 &&" << endl
<< " ( name.size( ) == language.size( ) ||" << endl
<< " name[language.size( )] == '_' ||" << endl
<< " name[language.size( )] == '.' ||" << endl
<< " name[language.size( )] == '@' );" << endl
<< " }" << endl;
Mode.CloseHelpers( stream );
}
//
// MessagePrototypeWriter
//
MessagePrototypeWriter::MessagePrototypeWriter( ostream &stream, string &indent )
: Writer( stream, indent )
{
// blank
}
string MessagePrototypeWriter::StreamableClassName( const Message &message )
{
const ClassConcept &concept = message.Settings->_Streamable;
return concept._Prefix + message.Name + concept._Postfix;
}
string MessagePrototypeWriter::ExceptionClassName( const Message &message )
{
const ClassConcept &concept = message.Settings->_Exception;
return concept._Prefix + message.Name + concept._Postfix;
}
bool MessagePrototypeWriter::ExceptionLocaleConstructor( const Message &message )
{
const MessageSettings &settings = *message.Settings;
return settings._Exception._LocaleExtra ||
( settings._Throw._Active && settings._Throw._LocaleExtra );
}
MessagePrototypeWriter::Placement MessagePrototypeWriter::Place( const Message &message,
const FunctionConcept &concept )
{
switch ( concept._Type )
{
case MEMBER_OF:
return { concept._Naming, message.Name, message.Parameters, concept._Constness, "" };
case MEMBER_AS:
return { StreamableClassName( message ), concept._Naming, { }, concept._Constness, "member_" };
case NONMEMBER:
break;
}
return { "", message.Name, message.Parameters, false, "" };
}
void MessagePrototypeWriter::FunctionPrototype( const string &returnType,
const string &className,
const string &prefix,
const string &postfix,
const string &name,
const ParameterList ¶meters,
bool constness,
bool localeParm,
bool streamFunc ) const
{
_Stream << returnType;
if ( ! returnType.empty( ) )
{
_Stream << ' ';
}
if ( ! className.empty( ) )
{
_Stream << className << "::";
}
_Stream << prefix << name << postfix << "( ";
if ( ! parameters.empty( ) || localeParm || streamFunc )
{
right( );
if ( streamFunc )
{
_Stream << "std::ostream &stream";
}
// Not every language's text uses every parameter.
for_each( parameters.begin( ), parameters.end( ),
ParameterWriter( _Stream, _Indent, streamFunc, streamFunc ) );
if ( localeParm )
{
if ( ! parameters.empty( ) || streamFunc )
{
_Stream << "," << endl << _Indent;
}
_Stream << "std::locale const &loc";
}
left( );
_Stream << " )";
}
else
{
_Stream << ')';
}
if ( constness )
{
_Stream << " const";
}
}
void MessagePrototypeWriter::LogFunctionPrototype( const string &returnType,
const string &className,
const string &prefix,
const string &postfix,
const string &name,
const ParameterList ¶meters,
bool constness,
const string &facility,
const string &level,
bool localeParm,
bool defaultVal ) const
{
_Stream << returnType << ' ';
if ( ! className.empty( ) )
{
_Stream << className << "::";
}
_Stream << prefix << name << postfix << "( ";
right( );
for_each( parameters.begin( ), parameters.end( ),
ParameterWriter( _Stream, _Indent ) );
if ( localeParm )
{
if ( ! parameters.empty( ) )
{
_Stream << "," << endl << _Indent;
}
_Stream << "std::locale const &loc";
}
if ( ! parameters.empty( ) || localeParm )
{
_Stream << "," << endl << _Indent;
}
_Stream << "int option_parameter";
if ( defaultVal )
{
_Stream << " = " << facility << " | " << level;
}
left( );
_Stream << " )";
if ( constness )
{
_Stream << " const";
}
}
void MessagePrototypeWriter::Declarations( const Message &message, const FunctionConcept &concept,
FunctionKind kind, bool inClass ) const
{
Placement const place = Place( message, concept );
for ( bool const loc : { true, false } )
{
if ( loc && ! concept._LocaleExtra )
{
continue;
}
_Stream << _Indent;
if ( kind == FunctionKind::Throw )
{
_Stream << "[[noreturn]] ";
}
if ( ! inClass )
{
_Stream << Mode.Export( );
}
if ( kind == FunctionKind::Log )
{
const LogConcept &log = message.Settings->_Log;
LogFunctionPrototype( ReturnType( kind ), "", concept._Prefix, concept._Postfix,
place.name, place.parameters, inClass && place.constness,
log._Facility, log._Level, loc, true );
}
else
{
FunctionPrototype( ReturnType( kind ), "", concept._Prefix, concept._Postfix,
place.name, place.parameters, inClass && place.constness, loc );
}
_Stream << ';' << endl << endl;
}
}
void MessagePrototypeWriter::WriteBaseClause( inheritance type, const string &parent ) const
{
if ( parent.empty( ) )
{
return;
}
right( );
_Stream << _Indent << ": ";
switch ( type )
{
case PUBLIC_INHERITANCE:
_Stream << "public";
break;
case PROTECTED_INHERITANCE:
_Stream << "protected";
break;
case PRIVATE_INHERITANCE:
_Stream << "private";
break;
}
_Stream << ' ' << parent << endl;
left( );
}
//
// MessageHeaderWriter
//
MessageHeaderWriter::MessageHeaderWriter( ostream &stream, string &indent )
: MessagePrototypeWriter( stream, indent )
{
// blank
}
void MessageHeaderWriter::operator( )( const Message &message )
{
const MessageSettings &settings = *message.Settings;
if ( settings._Streamable._Active )
{
StreamableClass( message );
}
if ( settings._Throw._Active || settings._Exception._Active )
{
ExceptionClass( message );
}
for ( FunctionKind const kind : AllKinds )
{
const FunctionConcept &concept = ConceptOf( settings, kind );
if ( concept._Active && concept._Type == NONMEMBER )
{
Declarations( message, concept, kind, false );
}
}
}
void MessageHeaderWriter::StreamableClass( const Message &message ) const
{
const MessageSettings &settings = *message.Settings;
string const className = StreamableClassName( message );
_Stream << _Indent << "//" << endl
<< _Indent << "// " << className << endl
<< _Indent << "//" << endl
<< _Indent << "class " << Mode.Export( ) << className << endl;
WriteBaseClause( settings._Streamable._InheritanceType, settings._Streamable._Parent );
_Stream << _Indent << '{' << endl;
right( );
right( );
if ( ! message.Parameters.empty( ) )
{
for_each( message.Parameters.begin( ), message.Parameters.end( ),
ParameterToVariableWriter( _Stream, _Indent ) );
_Stream << endl;
}
left( );
_Stream << _Indent << "public:" << endl;
right( );
_Stream << _Indent << ( message.Parameters.size( ) == 1 ? "explicit " : "" );
FunctionPrototype( "", "", "", "", className, message.Parameters, false, false );
_Stream << ';' << endl << endl;
_Stream << _Indent << "virtual ~" << className << "( );" << endl << endl;
for_each( message.Parameters.begin( ), message.Parameters.end( ),
ParameterToGetterWriter( _Stream, _Indent ) );
// MEMBER AS: the string / error / throw / syslog functions are members.
for ( FunctionKind const kind : AllKinds )
{
const FunctionConcept &concept = ConceptOf( settings, kind );
if ( concept._Active && concept._Type == MEMBER_AS )
{
Declarations( message, concept, kind, true );
}
}
left( );
_Stream << _Indent << "protected:" << endl;
right( );
_Stream << _Indent << "virtual void PrintOn( std::ostream &stream ) const;" << endl << endl
<< _Indent << "friend std::ostream &operator<< ( std::ostream &stream, "
<< className << " const &data );" << endl;
left( );
left( );
_Stream << _Indent << "};" << endl
<< endl
<< _Indent << "inline std::ostream &operator<< ( std::ostream &stream, "
<< className << " const &data )" << endl
<< _Indent << '{' << endl;
right( );
_Stream << _Indent << "data.PrintOn( stream );" << endl
<< _Indent << "return stream;" << endl;
left( );
_Stream << _Indent << '}' << endl << endl;
}
void MessageHeaderWriter::ExceptionClass( const Message &message ) const
{
const ClassConcept &concept = message.Settings->_Exception;
string const className = ExceptionClassName( message );
_Stream << _Indent << "//" << endl
<< _Indent << "// " << className << endl
<< _Indent << "//" << endl
<< _Indent << "class " << Mode.Export( ) << className << endl;
WriteBaseClause( concept._InheritanceType, concept._Parent );
_Stream << _Indent << '{' << endl;
right( );
right( );
_Stream << _Indent << "std::locale internal_locale;" << endl;
for_each( message.Parameters.begin( ), message.Parameters.end( ),
ParameterToVariableWriter( _Stream, _Indent ) );
bool const overrides = ! concept._Override.empty( );
if ( ! overrides )
{
_Stream << _Indent << "std::string internal_what;" << endl;
}
_Stream << endl;
left( );
_Stream << _Indent << "public:" << endl;
right( );
if ( ExceptionLocaleConstructor( message ) )
{
_Stream << _Indent;
FunctionPrototype( "", "", "", "", className, message.Parameters, false, true );
_Stream << ';' << endl << endl;
}
_Stream << _Indent << ( message.Parameters.size( ) == 1 ? "explicit " : "" );
FunctionPrototype( "", "", "", "", className, message.Parameters, false, false );
_Stream << ';' << endl << endl;
_Stream << _Indent << "virtual ~" << className << "( );" << endl << endl;
if ( overrides )
{
_Stream << _Indent << "/// The message in the exception's locale." << endl
<< _Indent << "std::string " << concept._Override << "( ) const noexcept override;" << endl << endl;
}
else
{
_Stream << _Indent << "/// The message in the exception's locale." << endl
<< _Indent << "virtual std::string What( ) const;" << endl << endl
<< _Indent << "/// The message in the exception's locale, as std::exception::what()." << endl
<< _Indent << "char const *what( ) const noexcept;" << endl << endl;
}
for_each( message.Parameters.begin( ), message.Parameters.end( ),
ParameterToGetterWriter( _Stream, _Indent ) );
left( );
left( );
_Stream << _Indent << "};" << endl << endl;
}
//
// MessageCodeWriter
//
MessageCodeWriter::MessageCodeWriter( ostream &stream, string &indent )
: MessagePrototypeWriter( stream, indent )
{
// blank
}
void MessageCodeWriter::operator( )( const Message &message )
{
const MessageSettings &settings = *message.Settings;
if ( NeedsStreamer( message ) )
{
TextFunction( message, false );
}
if ( NeedsBuilder( message ) )
{
TextFunction( message, true );
}
if ( settings._Streamable._Active )
{
StreamableClassBody( message );
}
if ( settings._Throw._Active || settings._Exception._Active )
{
ExceptionClass( message );
}
for ( FunctionKind const kind : AllKinds )
{
const FunctionConcept &concept = ConceptOf( settings, kind );
if ( concept._Active )
{
Definitions( message, concept, kind );
}
}
}
void MessageCodeWriter::TextFunction( const Message &message, bool builder )
{
_Stream << _Indent << Mode.TextFunctionLinkage( );
if ( builder )
{
_Stream << "void " << BuilderPrefix << message.Name << "( std::string &internal_out," << endl;
right( );
_Stream << _Indent << "[[maybe_unused]] std::locale const &loc";
for_each( message.Parameters.begin( ), message.Parameters.end( ),
ParameterWriter( _Stream, _Indent, true, true ) );
left( );
_Stream << " )";
}
else
{
FunctionPrototype( "void", "", StreamerPrefix, "", message.Name, message.Parameters,
false, false, true );
}
_Stream << endl
<< _Indent << '{' << endl;
right( );
if ( builder && ! message.Locales.empty( ) )
{
// One allocation for the usual case: the longest language's literal
// bytes, plus a guess per value.
size_t reserve = 0;
for ( const LocaleMessage &locale : message.Locales )
{
size_t size = 0;
for ( const TextElement &element : locale.TextElements )
{
size += element.Macro ? 16 : element.Text.size( );
}
reserve = max( reserve, size );
}
_Stream << _Indent << "internal_out.reserve( internal_out.size( ) + " << reserve << " );" << endl;
}
auto writeText = [this, builder]( const LocaleMessage &locale )
{
if ( builder )
WriteLocaleAppend( _Stream, _Indent, locale );
else
WriteLocaleText( _Stream, _Indent, locale );
};
const LocaleMessage *const fallback = message.DefaultLocale( );
// The other languages, most specific first: "tr_TR" is tried before "tr".
vector<const LocaleMessage *> languages;
for ( const LocaleMessage &locale : message.Locales )
{
if ( &locale != fallback )
{
languages.push_back( &locale );
}
}
stable_sort( languages.begin( ), languages.end( ),
[]( const LocaleMessage *a, const LocaleMessage *b )
{ return a->Language.size( ) > b->Language.size( ); } );
if ( fallback == nullptr )
{
_Stream << _Indent << "// no text" << endl
<< _Indent << "(void) " << ( builder ? "internal_out" : "stream" ) << ';' << endl;
}
else if ( languages.empty( ) )
{
writeText( *fallback );
}
else
{
_Stream << _Indent << "std::string const internal_locale_name( "
<< ( builder ? "loc" : "stream.getloc( )" ) << ".name( ) );" << endl
<< endl;
for ( size_t i = 0; i < languages.size( ); ++i )
{
_Stream << _Indent << ( i == 0 ? "if" : "else if" ) << " ( " << Mode.Helper( LocaleMatcherName )
<< "( internal_locale_name, \"" << languages[i]->Language << "\" ) )" << endl
<< _Indent << '{' << endl;
right( );
writeText( *languages[i] );
left( );
_Stream << _Indent << '}' << endl;
}
_Stream << _Indent << "else" << endl
<< _Indent << '{' << endl;
right( );
writeText( *fallback );
left( );
_Stream << _Indent << '}' << endl;
}
left( );
_Stream << _Indent << '}' << endl << endl;
}
void MessageCodeWriter::CallBuilder( const Message &message, const string &locale,
const string &argumentPrefix, const ParameterList ¶meters )
{
_Stream << _Indent << "std::string internal_result;" << endl
<< _Indent << BuilderPrefix << message.Name << "( internal_result, " << locale;
for_each( parameters.begin( ), parameters.end( ),
ParameterCallWriter( _Stream, _Indent, argumentPrefix, true ) );
_Stream << " );" << endl;
}
void MessageCodeWriter::CallStreamer( const Message &message, bool loc,
const string &stream, const string &argumentPrefix,
const ParameterList ¶meters )
{
string const target = stream.empty( ) ? "stream" : stream;
if ( stream.empty( ) )
{
_Stream << _Indent << "std::ostringstream stream;" << endl;
if ( loc )
{
_Stream << _Indent << "stream.imbue( loc );" << endl;
}
}
else if ( loc )
{
_Stream << _Indent << "std::locale const streamLocale( " << stream << ".imbue( loc ) );" << endl;
}
_Stream << _Indent << StreamerPrefix << message.Name << "( " << target;
for_each( parameters.begin( ), parameters.end( ),
ParameterCallWriter( _Stream, _Indent, argumentPrefix, true ) );
_Stream << " );" << endl;
if ( ! stream.empty( ) && loc )
{
_Stream << _Indent << stream << ".imbue( streamLocale );" << endl;
}
}
void MessageCodeWriter::Definitions( const Message &message, const FunctionConcept &concept, FunctionKind kind )
{
Placement const place = Place( message, concept );
// MEMBER AS passes the object's stored values, the others their own parameters.
const ParameterList &arguments = message.Parameters;
for ( bool const loc : { true, false } )
{
if ( loc && ! concept._LocaleExtra )
{
continue;
}
_Stream << _Indent << Mode.Linkage( );
if ( kind == FunctionKind::Log )
{
const LogConcept &log = message.Settings->_Log;
LogFunctionPrototype( ReturnType( kind ), place.className, concept._Prefix, concept._Postfix,
place.name, place.parameters, place.constness,
log._Facility, log._Level, loc, false );
}
else
{
FunctionPrototype( ReturnType( kind ), place.className, concept._Prefix, concept._Postfix,
place.name, place.parameters, place.constness, loc );
}
_Stream << endl
<< _Indent << '{' << endl;
right( );
switch ( kind )
{
case FunctionKind::String:
// Without a locale argument: the global locale, as a default
// constructed std::ostringstream would have used.
CallBuilder( message, loc ? "loc" : "std::locale( )", place.argumentPrefix, arguments );
_Stream << _Indent << "return internal_result;" << endl;
break;
case FunctionKind::Error:
CallStreamer( message, loc, "std::cerr", place.argumentPrefix, arguments );
_Stream << _Indent << message.Settings->_Error._ExitCall << ';' << endl;
break;
case FunctionKind::Throw:
_Stream << _Indent << "throw " << ExceptionClassName( message ) << "( ";
for_each( arguments.begin( ), arguments.end( ),
ParameterCallWriter( _Stream, _Indent, place.argumentPrefix, false ) );
if ( loc )
{
_Stream << ( arguments.empty( ) ? "" : ", " ) << "loc";
}
_Stream << ( arguments.empty( ) && ! loc ? ");" : " );" ) << endl;
break;
case FunctionKind::Log:
CallBuilder( message, loc ? "loc" : "std::locale( )", place.argumentPrefix, arguments );
_Stream << _Indent << "syslog( option_parameter, \"%s\", internal_result.c_str( ) );" << endl;
break;
}
left( );
_Stream << _Indent << '}' << endl << endl;
}
}
void MessageCodeWriter::ClassConstructor( const Message &message,
const string &parent,
const string &className,
bool loc,
bool exception )
{
_Stream << _Indent << Mode.Linkage( );
FunctionPrototype( "", className, "", "", className, message.Parameters, false, loc );
_Stream << endl;
vector<string> initializers;
if ( ! parent.empty( ) )
{
initializers.push_back( parent + "( )" );
}
if ( loc )
{
initializers.push_back( "internal_locale( loc )" );
}
for ( const Parameter ¶meter : message.Parameters )
{
initializers.push_back( "member_" + parameter.Name + "( " + parameter.Name + " )" );
}
if ( ! initializers.empty( ) )
{
right( );
for ( size_t i = 0; i < initializers.size( ); ++i )
{
_Stream << _Indent << ( i == 0 ? ": " : " " ) << initializers[i]
<< ( i + 1 < initializers.size( ) ? "," : "" ) << endl;
}
left( );
}
_Stream << _Indent << '{' << endl;
right( );
if ( exception && message.Settings->_Exception._Override.empty( ) )
{
// what() must not throw: format once, here.
_Stream << _Indent << "internal_what = What( );" << endl;
}
else
{
_Stream << _Indent << "// blank" << endl;
}
left( );
_Stream << _Indent << '}' << endl << endl;
}
void MessageCodeWriter::ExceptionClass( const Message &message )
{
string const className = ExceptionClassName( message );
string const &parent = message.Settings->_Exception._Parent;
if ( ExceptionLocaleConstructor( message ) )
{
ClassConstructor( message, parent, className, true, true );
}
ClassConstructor( message, parent, className, false, true );
_Stream << _Indent << Mode.Linkage( ) << className << "::~" << className << "( )" << endl
<< _Indent << '{' << endl;
right( );
_Stream << _Indent << "// blank" << endl;
left( );
_Stream << _Indent << '}' << endl
<< endl;
string const &overridden = message.Settings->_Exception._Override;
if ( ! overridden.empty( ) )
{
_Stream << _Indent << Mode.Linkage( ) << "std::string " << className << "::" << overridden
<< "( ) const noexcept" << endl
<< _Indent << '{' << endl;
right( );
CallBuilder( message, "internal_locale", "member_", message.Parameters );
_Stream << _Indent << "return internal_result;" << endl;
left( );
_Stream << _Indent << '}' << endl
<< endl;
return;
}
_Stream << _Indent << Mode.Linkage( ) << "std::string " << className << "::What( ) const" << endl
<< _Indent << '{' << endl;
right( );
CallBuilder( message, "internal_locale", "member_", message.Parameters );
_Stream << _Indent << "return internal_result;" << endl;
left( );
_Stream << _Indent << '}' << endl
<< endl
<< _Indent << Mode.Linkage( ) << "char const *" << className << "::what( ) const noexcept" << endl
<< _Indent << '{' << endl;
right( );
_Stream << _Indent << "return internal_what.c_str( );" << endl;
left( );
_Stream << _Indent << '}' << endl
<< endl;
}
void MessageCodeWriter::StreamableClassBody( const Message &message )
{
string const className = StreamableClassName( message );
ClassConstructor( message, message.Settings->_Streamable._Parent, className, false, false );
_Stream << _Indent << Mode.Linkage( ) << className << "::~" << className << "( )" << endl
<< _Indent << '{' << endl;
right( );
_Stream << _Indent << "// blank" << endl;
left( );
_Stream << _Indent << '}' << endl
<< endl
<< _Indent << Mode.Linkage( ) << "void " << className << "::PrintOn( std::ostream &stream ) const" << endl
<< _Indent << '{' << endl;
right( );
_Stream << _Indent << StreamerPrefix << message.Name << "( stream";
for_each( message.Parameters.begin( ), message.Parameters.end( ),
ParameterCallWriter( _Stream, _Indent, "member_", true ) );
_Stream << " );" << endl;
left( );
_Stream << _Indent << '}' << endl
<< endl;
}