module stream; /* Interfaces: * Stream interface for all streams * IStream interface for input streams * OStream interface for output streams * * Classes: * FStream i/o file stream * IFStream input file stream * OFStream output file stream * * Implemention templates: * StreamDefs mixin for Stream functions * IStreamDefs mixin for IStream functions * OStreamDefs mixin for OStream functions * FStreamDefs mixin for FStream functions */ /* private import std.c.stdlib; private import std.c.stdio; private import std.stdarg; private import std.ctype; */ private { import unformat; import std.format; import std.stdio; //import std.utf; import utf; import stdio; version( Win32 ) { import std.c.windows.windows; } } class StreamException : Exception { public: this() { super( "IO error" ); } this( char[] msg ) { super( msg ); } } class StreamBadState : StreamException { public: this() { super( "Bad stream state" ); } } class StreamReadFailure : StreamException { public: this() { super( "Stream read failure" ); } } class StreamEndOfFile : StreamException { public: this() { super( "Stream end" ); } } class FStreamException : StreamException { public: this() { super( "" ); version( Win32 ) { m_ecode = GetLastError(); } } this( char[] msg ) { super( msg ); version( Win32 ) { m_ecode = GetLastError(); } } char[] toString() { version( Win32 ) { return "Special windows message"; } } private: version( Win32 ) { uint m_ecode; } } interface Stream { /* * stream state information */ static const ubyte NOBIT = 0x0, EOFBIT = 0x1, FAILBIT = 0x2, BADBIT = 0x4, ALLBIT = 0x7; void setState( ubyte val, bit raise = true ); void clear( ubyte val = NOBIT, bit raise = true ); ubyte state(); // all state bits ubyte throwOn(); // show bits that raise exceptions ubyte throwOn( ubyte val ); // set bits that raise exceptions bit bad(); // bad stream state bit eof(); // end of file while extracting bit fail(); // a failure extracting from a stream bit good(); // no bits are set /* * read/write flags. BIN is missing. should it be added? */ static const ubyte IN = 0x1, OUT = 0x2, APP = 0x4, ATE = 0x8, TRUNC = 0x10; /* * temporary method to track the stream encoding format */ enum Format { UTF8 = 0x1, UTF16 = 0x2 } Format format(); // get the encoding format void format( Format fmt ); // set the encoding format } interface IStream : Stream { /* * unformatted read routines */ // read up to size bytes and return bytes read size_t readSome( void* buf, size_t size ); // read exactly size bytes IStream read( void* buf, size_t size ); /* * formatted read routines */ // reads to the first mismatched input char IStream get( out bit val ); IStream get( out byte val ); IStream get( out ubyte val ); IStream get( out short val ); IStream get( out ushort val ); IStream get( out int val ); IStream get( out uint val ); IStream get( out long val ); IStream get( out ulong val ); // IStream get( out cent val ); // IStream get( out ucent val ); IStream get( out float val ); IStream get( out double val ); IStream get( out real val ); // IStream get( out ireal val ); // IStream get( out ifloat val ); // IStream get( out idouble val ); // IStream get( out creal val ); // IStream get( out cfloat val ); // IStream get( out cdouble val ); IStream get( out char val ); IStream get( out wchar val ); IStream get( out dchar val ); // reads a word terminated by whitespace or EOF IStream get( out char[] val ); IStream get( out wchar[] val ); IStream get( out dchar[] val ); // reads a line terminated by either CR, LF, CR/LF, or EOF IStream getLine( out char[] val ); IStream getLine( out wchar[] val ); IStream getLine( out dchar[] val ); // returns a character to the stream, readable by get IStream unget( char val ); IStream unget( wchar val ); IStream unget( dchar val ); // formatted input base method // (perhaps this should be removed?) int readf( ... ); } interface OStream : Stream { /* * unformatted write routines */ // write exactly size bytes OStream write( void* buf, size_t size ); /* * formatted write routines */ // write primitive type OStream put( bit val ); OStream put( byte val ); OStream put( ubyte val ); OStream put( short val ); OStream put( ushort val ); OStream put( int val ); OStream put( uint val ); OStream put( long val ); OStream put( ulong val ); // OStream put( cent val ); // OStream put( ucent val ); OStream put( float val ); OStream put( double val ); OStream put( real val ); // OStream put( ireal val ); // OStream put( ifloat val ); // OStream put( idouble val ); // OStream put( creal val ); // OStream put( cfloat val ); // OStream put( cdouble val ); OStream put( char val ); OStream put( wchar val ); OStream put( dchar val ); // write char array OStream put( char[] val ); OStream put( wchar[] val ); OStream put( dchar[] val ); // writes a line, terminating with \n OStream putLine( char[] val ); OStream putLine( wchar[] val ); OStream putLine( dchar[] val ); // formatted output base method // (perhaps this should be removed?) void writef( ... ); } template StreamDefs() { public: void setState( ubyte val, bit raise = true ) { return clear( m_state | val, raise ); } void clear( ubyte val = NOBIT, bit raise = true ) { m_state = val & ALLBIT; if( !raise || m_state == NOBIT || m_raise == NOBIT ) return; else if( ( m_state & m_raise & BADBIT ) != 0 ) throw new StreamBadState(); else if( ( m_state & m_raise & FAILBIT ) != 0 ) throw new StreamReadFailure(); else if( ( m_state & m_raise & EOFBIT ) != 0 ) throw new StreamEndOfFile(); } ubyte state() { return m_state; } ubyte throwOn() { return m_raise; } ubyte throwOn( ubyte val ) { ubyte tmp = m_raise; m_raise = val & ALLBIT; return tmp; } bit bad() { return ( m_state & BADBIT ) != 0; } bit eof() { return ( m_state & EOFBIT ) != 0; } bit fail() { return ( m_state & ( BADBIT | FAILBIT ) ) != 0; } bit good() { return m_state == NOBIT; } Format format() { return m_fmt; } void format( Format fmt ) { m_fmt = fmt; } protected: void init() { m_state = NOBIT; m_raise = ALLBIT; m_fmt = Format.UTF8; } private: ubyte m_state, m_raise; Format m_fmt; } template IStreamDefs( alias readOp ) { protected: bit bitGet( out dchar val ) { bit doGetC( out char ch ) { switch( readOp( &ch, ch.sizeof ) ) { case ch.sizeof: return true; case 0: setState( EOFBIT, false ); return false; default: setState( FAILBIT ); return false; } } bit doGetWC( out wchar ch ) { switch( readOp( &ch, ch.sizeof ) ) { case ch.sizeof: return true; case 0: setState( EOFBIT, false ); return false; default: setState( FAILBIT ); return false; } } if( m_unbuf.length > 0 ) { val = m_unbuf[m_unbuf.length - 1]; m_unbuf.length = m_unbuf.length - 1; return true; } switch( format() ) { case Format.UTF8: return decode( val, &doGetC ); case Format.UTF16: return decode( val, &doGetWC ); default: assert( 0 ); return false; } } bit bitUnGet( dchar val ) { m_unbuf ~= val; return true; } void init() { } public: /* * unformatted input */ size_t readSome( void* buf, size_t size ) in { assert( buf != null || size == 0 ); } body { size_t bytesRead = readOp( buf, size ); if( bytesRead < size ) this.setState( EOFBIT, false ); return bytesRead; } IStream read( void* buf, size_t size ) in { assert( buf != null || size == 0 ); } body { if( readOp( buf, size ) < size ) setState( EOFBIT ); return this; } /* * formatted input */ IStream get( out bit val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out byte val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out ubyte val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out short val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out ushort val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out int val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out uint val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out long val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out ulong val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out float val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out double val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out real val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } // IStream get( out CharT val ) IStream get( out char val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out wchar val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out dchar val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } // IStream get( out CharT[] val ) IStream get( out char[] val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out wchar[] val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } IStream get( out dchar[] val ) { if( readf( &val ) != 1 ) setState( FAILBIT ); return this; } // IStream unget( CharT val ) IStream unget( char val ) { bitUnGet( cast(dchar) val ); return this; } IStream unget( wchar val ) { bitUnGet( cast(dchar) val ); return this; } IStream unget( dchar val ) { bitUnGet( val ); return this; } // IStream getLine( out CharT[] val ) IStream getLine( out char[] val ) { dchar[] buf; if( !getLine( buf ).eof() ) val = toUTF8( buf ); return this; } IStream getLine( out wchar[] val ) { dchar[] buf; if( !getLine( buf ).eof() ) val = toUTF16( buf ); return this; } IStream getLine( out dchar[] val ) { size_t len = 0; dchar ch; while( bitGet( ch ) ) { switch( ch ) { case '\r': if( !this.bitGet( ch ) ) { val.length = len; return this; } if( ch != '\n' ) this.unget( ch ); case '\n': val.length = len; return this; default: if( ++len > val.length ) val.length = len * 2; val[len-1] = ch; } } val.length = len; return this; } int readf( ... ) { return unFormat( &bitGet, &bitUnGet, _arguments, _argptr ); } private: dchar[] m_unbuf; } template OStreamDefs( alias writeOp ) { protected: void doPut( dchar val ) { bit doPutC( char ch ) { if( !writeOp( &ch, ch.sizeof ) ) { setState( BADBIT ); return false; } return true; } bit doPutWC( wchar ch ) { if( !writeOp( &ch, ch.sizeof ) ) { setState( BADBIT ); return false; } return true; } switch( format() ) { case Format.UTF8: return encode( &doPutC, val ); case Format.UTF16: return encode( &doPutWC, val ); default: assert( 0 ); return; } } void init() { } public: /* * unformatted output */ // read exactly size bytes OStream write( void* buf, size_t size ) in { assert( buf != null || size == 0 ); } body { if( writeOp( buf, size ) < size ) setState( BADBIT ); return this; } /* * formatted output */ // OStream put( NumT val ) OStream put( bit val ) { writef( val ); return this; } OStream put( byte val ) { writef( val ); return this; } OStream put( ubyte val ) { writef( val ); return this; } OStream put( short val ) { writef( val ); return this; } OStream put( ushort val ) { writef( val ); return this; } OStream put( int val ) { writef( val ); return this; } OStream put( uint val ) { writef( val ); return this; } OStream put( long val ) { writef( val ); return this; } OStream put( ulong val ) { writef( val ); return this; } OStream put( float val ) { writef( val ); return this; } OStream put( double val ) { writef( val ); return this; } OStream put( real val ) { writef( val ); return this; } // OStream put( CharT val ) OStream put( char val ) { writef( val ); return this; } OStream put( wchar val ) { writef( val ); return this; } OStream put( dchar val ) { writef( val ); return this; } OStream put( char[] val ) { writef( val ); return this; } OStream put( wchar[] val ) { writef( val ); return this; } OStream put( dchar[] val ) { writef( val ); return this; } OStream putLine( char[] val ) { static dchar newline = '\n'; writef( val, newline ); return this; } OStream putLine( wchar[] val ) { static dchar newline = '\n'; writef( val, newline ); return this; } OStream putLine( dchar[] val ) { static dchar newline = '\n'; writef( val, newline ); return this; } // void writef( ... ) void writef( ... ) { doFormat( &doPut, _arguments, _argptr ); } } template FStreamDefs() { protected: void open( char[] name, ubyte mode ) in { assert( name.length ); } body { close(); version( Win32 ) { uint access = 0, share = 0, create = 0; if( mode & IN ) { access |= GENERIC_READ; share |= FILE_SHARE_READ; create = ( mode & TRUNC ) ? TRUNCATE_EXISTING : OPEN_EXISTING; } if( mode & OUT ) { access |= GENERIC_WRITE; create = ( mode & TRUNC ) ? CREATE_ALWAYS : OPEN_ALWAYS; } if( !name.length || name[name.length-1] != 0 ) name ~= 0; m_file = CreateFileA( name, access, share, null, create, 0, null ); if( m_file != INVALID_HANDLE_VALUE ) return; m_mode = mode; } throw new FStreamException( "Error opening file " ~ name ); } size_t readFile( void* buf, size_t size ) in { assert( buf != null || size == 0 ); } body { size_t bytesRead; version( Win32 ) { if( ReadFile( m_file, buf, size, &bytesRead, null ) == 0 ) setState( BADBIT ); } return bytesRead; } size_t writeFile( void* buf, size_t size ) in { assert( buf != null || size == 0 ); } body { size_t bytesWritten; version( Win32 ) { if( WriteFile( m_file, buf, size, &bytesWritten, null ) == 0 ) setState( BADBIT ); } return bytesWritten; } void init() { version( Win32 ) { m_file = null; } else { m_file = 0; } m_mode = 0; } public: void close() { version( Win32 ) { if( m_file == null ) return; CloseHandle( m_file ); m_file = null; m_mode = 0; } } private: version( Win32 ) { HANDLE m_file; } else { int m_file; } ubyte m_mode; } class IFStream : IStream { mixin StreamDefs SD; mixin FStreamDefs!() FSD; mixin IStreamDefs!( FSD.readFile ) ISD; public: this() { SD.init(); FSD.init(); ISD.init(); } ~this() { close(); } void open( char[] name, ubyte mode = Stream.IN ) { mode = ( mode & !Stream.OUT ) | Stream.IN; FSD.open( name, mode ); } } class OFStream : OStream { mixin StreamDefs SD; mixin FStreamDefs!() FSD; mixin OStreamDefs!( FSD.writeFile ) OSD; public: this() { SD.init(); FSD.init(); OSD.init(); } ~this() { close(); } void open( char[] name, ubyte mode = Stream.OUT ) { mode = ( mode & !Stream.IN ) | Stream.OUT; FSD.open( name, mode ); } } class FStream : IStream, OStream { mixin StreamDefs SD; mixin FStreamDefs!() FSD; mixin IStreamDefs!( FSD.readFile ) ISD; mixin OStreamDefs!( FSD.writeFile ) OSD; public: this() { SD.init(); FSD.init(); ISD.init(); OSD.init(); } ~this() { close(); } void open( char[] name, ubyte mode = Stream.OUT ) { mode = ( mode & !Stream.IN ) | Stream.OUT; FSD.open( name, mode ); } } unittest { try { int ival; char[] buf1, buf2; OFStream f1 = new OFStream(); f1.open( "test.txt" ); f1.put( 1 ).putLine( cast(char[]) "hello world" ).putLine( cast(char[]) "hello again" ); f1.close(); IFStream f2 = new IFStream(); f2.open( "test.txt" ); f2.get( ival ).get( buf1 ).get( buf2 ); writefln( ival ); writefln( buf1 ); writefln( buf2 ); // first getLine eats CRLF f2.getLine( buf1 ).getLine( buf1 ); writefln( buf1 ); f2.close(); } catch( StreamException e ) { printf( "ERROR: %.*s\n", e.toString() ); assert( false ); } }