Code View

mstring / mstring-1.3.1.2 / src / MMessage.cpp
// 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 &parameters,
                                                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 &parameters,
                                                   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 &parameters )
{
  _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 &parameters )
{
  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 &parameter : 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;
}