xostream.h

// xostream.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2008

#pragma once
#ifndef Ceda_cxUtils_xostream_H
#define Ceda_cxUtils_xostream_H

#include "cxUtils.h"
#include "xchar.h"
#include "BasicTypeSizes.h"
#include "Detect.h"
#include <string>
#include <string.h>

namespace ceda
{

struct IOutputStream;
class IndentingOutputStreamToOStream;

typedef int64 streamsize;

///////////////////////////////////////////////////////////////////////////////////////////////////
// xostream

/*
An xostream writes text encoded in UTF-8.  Code units are of type xchar.
*/
class cxUtils_API xostream
{
    cxNotCloneable(xostream)
    
public:
    xostream(IOutputStream& outs);
    ~xostream();
    
    void close();
    void flush();
    void endl();
    void ends();

    /////////////////////////////////////// unformatted output /////////////////////////////////////

    // Write the given string directly to the output stream, without accounting for field width
    // fill character and justification (contrast with put(str,numChars) method)
    void write(const xchar* str, ssize_t numChars);
    
    // Write the given bytes to the underlying output stream
    void writebytes(const uint8* buffer, ssize_t numBytes);
    
    /////////////////////////////////////// formatted output /////////////////////////////////////
    /*
    The put functions are used for formatted output - meaning that
    items are written to the output stream accounting for the current field width, fill character
    and justification.
    
    Note: it appears that in STL 'put' is actually used for unformatted output, so some renaming is
    in order.

    VS2008
         std::basic_ostream<char> writes an unsigned char as a character not an integer.
         std::basic_ostream<wchar_t> writes an unsigned char as an integer not a character.
    This behaviour seems unnecessarily inconsistent.
    */
    
    void put(bool);
    void put(char);
    void put(signed char);
    void put(unsigned char);
    void put(wchar_t);
    void put(char16_t);
    void put(char32_t);
    void put(short);
    void put(unsigned short);
    void put(int);
    void put(unsigned int);
    void put(long);
    void put(unsigned long);
    void put(long long);
    void put(unsigned long long);
    void put(float);
    void put(double);
    void put(long double);
    void put(const void* f); 
    void put(const char8* str, ssize_t numChars);
    inline void put(ConstString8Z str) { put(str, strlen(str)); }
    inline void put(const std::string& s) { put(s.data(), s.size()); }
    
    void put_internal(const xchar* buffer, ssize_t ln, ssize_t tn);

    typedef xostream& (*Manipulator)(xostream& os);
    xostream& operator<<(Manipulator f)
    {
        return f(*this);
    }

	streamsize precision() const;
	streamsize precision(streamsize p);
	streamsize width() const;
	streamsize width(streamsize w);
	
	void Indent(ssize_t offset);
	ssize_t GetIndent() const;
        
    IndentingOutputStreamToOStream* m_os;
};

inline xostream& operator<<(xostream& os, bool f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, char f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, signed char f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, unsigned char f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, wchar_t f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, char16_t f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, char32_t f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, short f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, unsigned short f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, int f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, unsigned int f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, long f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, unsigned long f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, long long f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, unsigned long long f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, float f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, double f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, long double f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, const void* f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, ConstString8Z f) { os.put(f); return os; }
inline xostream& operator<<(xostream& os, const std::string& s) { os.put(s); return os; }

template <typename T> using xostream_insertion_t = decltype(std::declval<xostream&>() << std::declval<T const &>());
template <typename T> using has_xostream_insertion = is_detected<xostream_insertion_t, T>;

// If a class has a method with signature
//     void Write(xostream&) const 
// then automatically allow operator<< to be used.
template <typename T> using Write_xostream_method_t = decltype(std::declval<const T&>().Write(std::declval<xostream&>()));
template <typename T> using has_Write_xostream_method = is_detected<Write_xostream_method_t, T>;

template <typename T, std::enable_if_t<has_Write_xostream_method<T>::value, int> = 0>
inline xostream& operator<<(xostream& os, const T& x) 
{
    x.Write(os); 
    return os; 
}

// store function pointer and argument value
template<typename Arg>
struct SingleArgManip
{	
    SingleArgManip(void (*fn)(xostream&, Arg), Arg arg)
	    : m_fn(fn), m_arg(arg)
	{
	}

    void (*m_fn)(xostream&, Arg);	    // the function pointer
    Arg m_arg;	                        // the argument value
};

// insert by calling function with output stream and argument
template<typename Arg> 
inline xostream& operator<<(xostream& os, const SingleArgManip<Arg>& m)
{
	(*m.m_fn)(os, m.m_arg);
	return os;
}

typedef int ios_flags;

cxUtils_API SingleArgManip<ssize_t> setprecision(ssize_t p);
cxUtils_API SingleArgManip<ssize_t> setw(ssize_t w);
cxUtils_API SingleArgManip<xchar> setfill(xchar c);
cxUtils_API SingleArgManip<ios_flags> setiosflags(ios_flags);
cxUtils_API SingleArgManip<ios_flags> resetiosflags(ios_flags);
cxUtils_API SingleArgManip<int> setbase(int);

inline xostream& endl(xostream& os)
{
    os.endl();
    return os;
}

inline xostream& ends(xostream& os)
{
    os.ends();
    return os;
}

inline xostream& flush(xostream& os)
{
    os.flush();
    return os;
}

#define mDeclareManipulator(x)  cxUtils_API xostream& x(xostream& os);

mDeclareManipulator(boolalpha)
mDeclareManipulator(dec)
mDeclareManipulator(fixed)
mDeclareManipulator(hex)
mDeclareManipulator(internal)
mDeclareManipulator(left)
mDeclareManipulator(noboolalpha)
mDeclareManipulator(noshowbase)
mDeclareManipulator(noshowpoint)
mDeclareManipulator(noshowpos)
mDeclareManipulator(noskipws)
mDeclareManipulator(nounitbuf)
mDeclareManipulator(nouppercase)
mDeclareManipulator(oct)
mDeclareManipulator(right)
mDeclareManipulator(scientific)
mDeclareManipulator(showbase)
mDeclareManipulator(showpoint)
mDeclareManipulator(showpos)
mDeclareManipulator(skipws)
mDeclareManipulator(unitbuf)
mDeclareManipulator(uppercase)

mDeclareManipulator(fixedorscientific)

///////////////////////////////////////////////////////////////////////////////////////////////////
// Indenter

// Used to apply an indent in a lexical scope to the given xostream

class cxUtils_API Indenter
{
    cxNotCloneable(Indenter)
public:
    Indenter(xostream& os, ssize_t indent = 4);
    ~Indenter();
private:
    xostream& m_os;
    ssize_t m_indent;
};

///////////////////////////////////////////////////////////////////////////////////////////////////

// Write n characters starting at position str to stdout.
cxUtils_API void WriteToStdout(const xchar* str, ssize_t n);

extern cxUtils_API xostream xcout;

///////////////////////////////////////////////////////////////////////////////////////////////////
/*
When printing randomly generated char values in tests it is all too easy to produce strings which 
are not valid UTF-8, and this can cause StringConversionException exceptions to be thrown 
(see xstring.h).

Rather than use

    os << x

you can use 

    os << CharToInt(x)

to deal with the case where x is of type char, and you want it to be printed as an int not a char
because it might not be in the range 0x00 to 0x7f.
*/

template <typename T>
struct MapCharToInt { using type = T; };

template <>
struct MapCharToInt<char> { using type = int; };

template <typename T>
auto CharToInt(const T& x) 
{ 
    return static_cast<typename MapCharToInt<T>::type>(x); 
}

} // namespace ceda

#endif // include guard