Code View

mstring / mstring-1.3.1.2 / src / MString.cpp
// SPDX-License-Identifier: MIT
#include "MString.hh"

#include <algorithm>
#include <cctype>
#include <fstream>
#include <iterator>
#include <ostream>

#include "MException.hh"
#include "MIncludes.hh"
#include "MMessage.hh"
#include "MNamespace.hh"
#include "MText.hh"
#include "MUsing.hh"

using namespace std;

#ifndef MSTRING_VERSION
#define MSTRING_VERSION "unknown"
#endif

namespace
{
  bool Any( const Module &module, bool ( *predicate )( const MessageSettings & ) )
  {
    return any_of( module.Messages.begin( ), module.Messages.end( ),
                   [predicate]( const Message &message ) { return predicate( *message.Settings ); } );
  }

  bool HasStreamable( const MessageSettings &s ) { return s._Streamable._Active; }
  bool HasString( const MessageSettings &s ) { return s._String._Active; }
  bool HasError( const MessageSettings &s ) { return s._Error._Active; }
  bool HasLog( const MessageSettings &s ) { return s._Log._Active; }
  bool HasException( const MessageSettings &s ) { return s._Exception._Active || s._Throw._Active; }

  /// Standard headers the declarations need, then the user's `$INCLUDE`s.
  vector<string> InterfaceIncludes( const Module &module )
  {
    vector<string> includes;
    bool const classes = Any( module, HasStreamable ) || Any( module, HasException );

    if ( Any( module, HasStreamable ) )
      includes.push_back( "<iosfwd>" );

    includes.push_back( "<locale>" );

    if ( Any( module, HasString ) || Any( module, HasException ) )
      includes.push_back( "<string>" );

    if ( classes )
      includes.push_back( "<type_traits>" );

    if ( Any( module, HasException ) )
      includes.push_back( "<exception>" );

    if ( Any( module, HasLog ) )
      includes.push_back( "<syslog.h>" );

    for ( const string &include : module.Includes )
    {
      if ( find( includes.begin( ), includes.end( ), include ) == includes.end( ) )
        includes.push_back( include );
    }

    return includes;
  }

  string GuardMacro( const string &fileName )
  {
    string macro = "MSTRING_";

    for ( char const c : fileName )
    {
      macro += isalnum( static_cast<unsigned char>( c ) )
             ? static_cast<char>( toupper( static_cast<unsigned char>( c ) ) )
             : '_';
    }

    return macro;
  }

  void Banner( ostream &stream, const string &fileName, const string &input )
  {
    stream << "// " << fileName << endl
           << "//" << endl
           << "// Generated by mstring " << MSTRING_VERSION;

    if ( ! input.empty( ) )
    {
      stream << " from " << input;
    }

    stream << ". Do not edit: edit the message file" << endl
           << "// and run mstring again." << endl
           << endl;
  }

  /// Includes, namespaces, `$USING`s and all declarations.
  void WriteDeclarations( ostream &stream, const Module &module )
  {
    string indent;
    vector<string> const includes = InterfaceIncludes( module );

    for_each( includes.begin( ), includes.end( ), IncludeFileWriter( stream, indent ) );
    stream << endl;

    for_each( module.Namespaces.begin( ), module.Namespaces.end( ),
              NamespaceHeaderWriter( stream, indent ) );

    if ( ! module.Usings.empty( ) )
    {
      // Inside the namespace: the parameter types in the declarations may
      // rely on them, and so they do not leak into every includer's scope.
      for_each( module.Usings.begin( ), module.Usings.end( ), ImportedEntityWriter( stream, indent ) );
      stream << endl;
    }

    for_each( module.Messages.begin( ), module.Messages.end( ), MessageHeaderWriter( stream, indent ) );
    for_each( module.Namespaces.rbegin( ), module.Namespaces.rend( ),
              NamespaceTailWriter( stream, indent ) );
  }
}

void ValidateModule( const Module &module )
{
  if ( module.Inline && ! module.HeaderExtension )
  {
    throw MException( "$MODULE " + module.Name + ": INLINE puts the definitions into the "
                      "header, it cannot be combined with NOHEADER" );
  }

  for ( const Message &message : module.Messages )
  {
    const MessageSettings &settings = *message.Settings;

    if ( ( settings._Exception._Active || settings._Throw._Active ) &&
         ! settings._Exception._Override.empty( ) && settings._Exception._Parent.empty( ) )
    {
      throw MException( "message '" + message.Name + "': $EXCEPTION OVERRIDE " +
                        settings._Exception._Override + " overrides a method of the base class, "
                        "but the exception has none (INHERITED)" );
    }

    const pair<const char *, const FunctionConcept *> concepts[] =
    {
      { "$STRING", &settings._String }, { "$ERROR", &settings._Error },
      { "$THROW", &settings._Throw }, { "$SYSLOG", &settings._Log },
    };

    for ( auto const &[directive, concept] : concepts )
    {
      if ( concept->_Active && concept->_Type == MEMBER_AS && ! settings._Streamable._Active )
      {
        throw MException( "message '" + message.Name + "': " + directive + " MEMBER AS " +
                          concept->_Naming + " makes a member of the streamable class, "
                          "but $STREAMABLE is not enabled" );
      }
    }
  }
}

string HeaderFileName( const Module &module )
{
  if ( ! module.HeaderExtension )
  {
    return "";
  }

  return module.Name + '.' + *module.HeaderExtension;
}

string SourceFileName( const Module &module )
{
  if ( ! module.SourceExtension )
  {
    return "";
  }

  return module.Name + '.' + *module.SourceExtension;
}

void WriteCppInterface( ostream &stream, const Module &module, const string &input )
{
  Mode.exportMacro = module.Export;
  string const fileName = HeaderFileName( module );
  string const guard = GuardMacro( fileName );

  Banner( stream, fileName, input );
  stream << "#ifndef " << guard << endl
         << "#define " << guard << endl
         << endl;
  WriteDeclarations( stream, module );

  if ( module.Inline )
  {
    stream << endl
           << "// Definitions ($MODULE ... INLINE)" << endl
           << endl;
    WriteDefinitions( stream, module );
  }

  stream << endl
         << "#endif" << endl;
}

void WriteCppImplementation( ostream &stream, const Module &module, const string &input )
{
  Mode.exportMacro = module.Export;
  string const header = HeaderFileName( module );

  Banner( stream, SourceFileName( module ), input );

  if ( ! header.empty( ) )
  {
    stream << "#include \"" << header << '"' << endl
           << endl;
  }
  else
  {
    WriteDeclarations( stream, module );
    stream << endl;
  }

  WriteDefinitions( stream, module );
}

void WriteDefinitions( ostream &stream, const Module &module )
{
  string indent;
  Mode.inlineDefinitions = module.Inline;

  stream << "#include <sstream>" << endl
         << "#include <string>" << endl;

  bool const builders = any_of( module.Messages.begin( ), module.Messages.end( ), NeedsBuilder );

  if ( builders )
  {
    stream << "#include <charconv>" << endl
           << "#include <locale>" << endl
           << "#include <string_view>" << endl
           << "#include <type_traits>" << endl;
  }

  if ( Any( module, HasError ) )
  {
    stream << "#include <cstdlib>" << endl
           << "#include <iostream>" << endl;
  }

  stream << endl;

  bool const languages = any_of( module.Messages.begin( ), module.Messages.end( ),
                                 []( const Message &message ) { return message.Locales.size( ) > 1; } );

  if ( languages )
  {
    WriteLocaleMatcher( stream );
  }

  if ( builders )
  {
    WriteAppendHelper( stream );
  }

  for_each( module.Namespaces.begin( ), module.Namespaces.end( ),
            NamespaceHeaderWriter( stream, indent ) );
  for_each( module.Messages.begin( ), module.Messages.end( ), MessageCodeWriter( stream, indent ) );
  for_each( module.Namespaces.rbegin( ), module.Namespaces.rend( ),
            NamespaceTailWriter( stream, indent ) );
}

bool WriteIfChanged( const string &path, const string &content )
{
  {
    ifstream existing( path, ios::binary );

    if ( existing )
    {
      string const old( ( istreambuf_iterator<char>( existing ) ), istreambuf_iterator<char>( ) );

      if ( old == content )
      {
        return false;
      }
    }
  }

  ofstream output( path, ios::binary | ios::trunc );
  output << content;
  output.close( );

  if ( ! output )
  {
    throw MException( "cannot write '" + path + "'" );
  }

  return true;
}