ExtStream.st
author claus
Fri, 16 Jul 1993 11:39:45 +0200
changeset 1 a27a279701f8
child 2 6526dde5f3ac
permissions -rw-r--r--
Initial revision

"
 COPYRIGHT (c) 1988-93 by Claus Gittinger
              All Rights Reserved

 This software is furnished under a license and may be used
 only in accordance with the terms of that license and with the
 inclusion of the above copyright notice.   This software may not
 be provided or otherwise made available to, or used by, any
 other person.  No title to or ownership of the software is
 hereby transferred.
"

ReadWriteStream subclass:#ExternalStream
       instanceVariableNames:'filePointer mode unBuffered binary'
       classVariableNames:'lobby'
       poolDictionaries:''
       category:'Streams-External'
!

ExternalStream comment:'

COPYRIGHT (c) 1988-93 by Claus Gittinger
              All Rights Reserved

ExternalStream defines protocol common to Streams which have a file-descriptor and 
represent some File or CommunicationChannel of the underlying OperatingSystem.
ExternalStream is abstract; concrete classes are FileStream, PipeStream etc.
ExternalStreams can be in two modes: text (the default) and binary.
In text-mode, the elements read/written are characters; while in binary-mode the basic
elements are bytes which read/write as SmallIntegers in the range 0..255.

%W% %E%

written 88 by claus
'!

%{
#include <stdio.h>
%}

!ExternalStream class methodsFor:'initialization'!

initialize
    lobby isNil ifTrue:[
        lobby := Registry new.

        "want to get informed when returning from snapshot"
        ObjectMemory addDependent:self
    ]
!

reOpenFiles
    "reopen all files (if possible) after a snapShot load"

    lobby contentsDo:[:aFileStream |
        aFileStream reOpen
    ]
!

update:something
    "have to reopen files when returning from snapshot"

    something == #returnFromSnapshot ifTrue:[
        self reOpenFiles
    ]
! !

!ExternalStream methodsFor:'instance release'!

disposed
    "some Stream has been collected - close the file if not already done"

    self closeFile
!

closeFile
    "low level close - may be redefined in subclasses"

%{  /* NOCONTEXT */

    fclose(MKFD(_INST(filePointer)));
%}
! !

!ExternalStream methodsFor:'private'!

reOpen
    "sent after snapin to reopen streams.
     cannot reopen here since I am abstract and have no device knowledge"

    self class name print. ': cannot reOpen stream - stream closed' printNewline.
    filePointer := nil.
    lobby unregister:self.
! !

!ExternalStream methodsFor:'error handling'!

errorNotOpen
    "report an error, that the stream has not been opened"

    ^ self error:(self class name , ' not open')
!

errorReadOnly
    "report an error, that the stream is a readOnly stream"

    ^ self error:(self class name , ' is readonly')
!

errorWriteOnly
    "report an error, that the stream is a writeOnly stream"

    ^ self error:(self class name , ' is writeonly')
!

errorNotBinary
    "report an error, that the stream is not in binary mode"

    ^ self error:(self class name , ' is not in binary mode')
!

argumentMustBeInteger
    "report an error, that the argument must be an integer"

    ^ self error:'argument must be an integer'
!

argumentMustBeCharacter
    "report an error, that the argument must be a character"

    ^ self error:'argument must be a character'
!

argumentMustBeString
    "report an error, that the argument must be a string"

    ^ self error:'argument must be a string'
! !

!ExternalStream methodsFor:'accessing'!

readonly
    "set access mode to readonly"

    mode := #readonly
!

writeonly
    "set access mode to writeonly"

    mode := #writeonly
!

readwrite
    "set access mode to readwrite"

    mode := #readwrite
!

filePointer
    "return the filePointer of the receiver -
     notice: for portability stdio is used; this means you will get
     a FILE * - not a fileDescriptor"

    ^ filePointer
!

fileDescriptor
    "return the fileDescriptor of the receiver -
     notice: this one returns the underlying OSs fileDescriptor -
     this may not be available on all platforms (i.e. non unix systems)."

%{  /* NOCONTEXT */

    FILE *f;

    if (_INST(filePointer) != nil) {
        f = MKFD(_INST(filePointer));
        RETURN ( MKOBJ(fileno(f)) );
    }
%}
.
    ^ self errorNotOpen
!

buffered:aBoolean
    "turn buffering on or off"

    unBuffered := aBoolean not
!

binary
    "switch to binary mode"

    binary := true
!

text
    "switch to text mode"

    binary := false
!

contents
    "return the contents of the file as a Text-object"

    |text|

    text := Text new.
    [self atEnd] whileFalse:[
        text add:(self nextLine)
    ].
    ^ text
! !

!ExternalStream methodsFor:'basic'!

open
    "open the stream
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

create
    "create the stream
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

position
    "return the position
     - this must be redefined in subclass"

    ^ self subclassResponsibility
!

position:anInteger
    "set the position
     - this must be redefined in subclass"

    ^ self subclassResponsibility
! !

!ExternalStream methodsFor:'low level I/O'!

ioctl:ioctlNumber with:arg
    "to provide a simple ioctl facility"

%{  /* NOCONTEXT */

    FILE *f;
    int ret, ioNum, ioArg;
    extern OBJ ErrorNumber;
    extern errno;

    if (_INST(filePointer) != nil) {
        if (_isSmallInteger(ioctlNumber) && _isSmallInteger(arg)) {
            ioNum = _intVal(ioctlNumber);
            ioArg = _intVal(arg);
            f = MKFD(_INST(filePointer));
            ret = ioctl(fileno(f), ioNum, ioArg);
            if (ret >= 0) {
                RETURN ( _MKSMALLINT(ret) );
            }
            ErrorNumber = _MKSMALLINT(errno);
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self primitiveFailed
!

nextByte
    "read the next byte return it as an Integer
     nil on error.  Use with care - non object oriented i/o"

%{  /* NOCONTEXT */

    FILE *f;
    unsigned char byte;
    int cnt;
    extern OBJ ErrorNumber;
    extern errno;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            _immediateInterrupt = 1;
            if (_INST(unBuffered) == true) {
                cnt = read(fileno(f), &byte, 1);
            } else {
		if (_INST(mode) == _readwrite)
		    fseek(f, 0L, 1); /* needed in stdio */
                cnt = fread(&byte, 1, 1, f);
            }
            _immediateInterrupt = 0;
            if (cnt == 1) {
                if (_INST(position) != nil)
                    _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + 1);
                RETURN ( _MKSMALLINT(byte) );
            }
            if (cnt < 0) {
                ErrorNumber = _MKSMALLINT(errno);
            }
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self errorWriteOnly
!

nextBytes:count into:anObject
    "read the next count bytes into an object and return the number of
     bytes read or nil on error.
     Use with care - non object oriented i/o"

    ^ self nextBytes:count into:anObject startingAt:1
!

nextBytes:count into:anObject startingAt:start
    "read the next count bytes into an object and return the number of
     bytes read or nil on error.
     Use with care - non object oriented i/o"

%{  /* NOCONTEXT */

    FILE *f;
    int cnt, offs;
    int objSize;
    char *cp;
    extern OBJ ErrorNumber;
    extern errno;
    OBJ pos;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            if (_isSmallInteger(count) && _isSmallInteger(start)) {
                cnt = _intVal(count);
                offs = _intVal(start) - 1;
                f = MKFD(_INST(filePointer));
                objSize = _Size(anObject) - OHDR_SIZE;
                if ((offs >= 0) && (cnt >= 0) && (objSize >= (cnt + offs))) {
                    cp = (char *)_InstPtr(anObject) + OHDR_SIZE + offs;
                    _immediateInterrupt = 1;
                    if (_INST(unBuffered) == true) {
                        cnt = read(fileno(f), cp, cnt);
                    } else {
		        if (_INST(mode) == _readwrite)
		            fseek(f, 0L, 1); /* needed in stdio */
                        cnt = fread(cp, 1, cnt, f);
                    }
                    _immediateInterrupt = 0;
                    if (cnt >= 0) {
                        pos = _INST(position);
                        if (pos != nil)
                            _INST(position) = _MKSMALLINT(_intVal(pos) + cnt);
                        RETURN ( _MKSMALLINT(cnt) );
                    }
                    ErrorNumber = _MKSMALLINT(errno);
                    RETURN ( nil );
                }
            }
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    self primitiveFailed
!

nextPutByte:aByteValue
    "write a byte"

%{  /* NOCONTEXT */

    FILE *f;
    char c;
    extern OBJ ErrorNumber;
    extern errno;
    OBJ pos;
    int cnt;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _readonly) {
            if (_isSmallInteger(aByteValue)) {
                c = _intVal(aByteValue);
                f = MKFD(_INST(filePointer));
                _immediateInterrupt = 1;
		if (_INST(mode) == _readwrite)
		    fseek(f, 0L, 1); /* needed in stdio */
                cnt = fwrite(&c, 1, 1, f);
                _immediateInterrupt = 0;
                if (cnt == 1) {
                    if (_INST(unBuffered) == true) {
                        fflush(f);
                    }
                    pos = _INST(position);
                    if (pos != nil)
                        _INST(position) = _MKSMALLINT(_intVal(pos) + 1);
                    RETURN ( self );
                }
                ErrorNumber = _MKSMALLINT(errno);
            }
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    self primitiveFailed
!

nextPutBytes:count from:anObject
    "write count bytes from an object starting at index start.
     return the number of bytes written or nil on error.
     Use with care - non object oriented i/o"

    ^ self nextPutBytes:count from:anObject startingAt:1
!

nextPutBytes:count from:anObject startingAt:start
    "write count bytes from an object starting at index start.
     return the number of bytes written or nil on error.
     Use with care - non object oriented i/o"

%{  /* NOCONTEXT */

    FILE *f;
    int cnt, offs;
    int objSize;
    char *cp;
    extern OBJ ErrorNumber;
    extern errno;
    OBJ pos;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _readonly) {
            if (_isSmallInteger(count) && _isSmallInteger(start)) {
                cnt = _intVal(count);
                offs = _intVal(start) - 1;
                f = MKFD(_INST(filePointer));

                objSize = _Size(anObject) - OHDR_SIZE;
                if ( (offs >= 0) && (cnt >= 0) && (objSize >= (cnt + offs)) ) {
                    cp = (char *)_InstPtr(anObject) + OHDR_SIZE + offs;
                    _immediateInterrupt = 1;
                    if (_INST(unBuffered) == true) {
                        cnt = write(fileno(f), cp, cnt);
                    } else {
		        if (_INST(mode) == _readwrite)
		            fseek(f, 0L, 1); /* needed in stdio */
                        cnt = fwrite(cp, 1, cnt, f);
                    }
                    _immediateInterrupt = 0;
                    if (cnt >= 0) {
                        pos = _INST(position);
                        if (pos != nil)
                            _INST(position) = _MKSMALLINT(_intVal(pos) + cnt);
                        RETURN ( _MKSMALLINT(cnt) );
                    }
                    ErrorNumber = _MKSMALLINT(errno);
                    RETURN ( nil );
                }
            }
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    self primitiveFailed
! !

!ExternalStream methodsFor:'character I/O'!

peek
    "return the character to be read next without advancing read position"

%{  /* NOCONTEXT */

    FILE *f;
    REGISTER int c;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            c = getc(f);
            if (c != EOF) {
                ungetc(c, f);
                if (_INST(binary) == true) {
                    RETURN ( _MKSMALLINT(c & 0xFF) );
                }
                RETURN ( _MKCHARACTER(c & 0xFF) );
            }
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self errorWriteOnly
!

next
    "return the character; advance read position"

%{  /* NOCONTEXT */

    FILE *f;
    int c;
    OBJ pos;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            _immediateInterrupt = 1;
            if (_INST(unBuffered) == true) {
                if (read(fileno(f), &c, 1) != 1)
                    c = EOF;
            } else {
		if (_INST(mode) == _readwrite)
		    fseek(f, 0L, 1); /* needed in stdio */
                c = getc(f);
            }
            _immediateInterrupt = 0;
            if (c != EOF) {
                pos = _INST(position);
                if (pos != nil) {
                    _INST(position) = _MKSMALLINT(_intVal(pos) + 1);
                }
                if (_INST(binary) == true) {
                    RETURN ( _MKSMALLINT(c & 0xFF) );
                }
                RETURN ( _MKCHARACTER(c & 0xFF) );
            }
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self errorWriteOnly
!

nextPut:aCharacter
    "write the argument, aCharacter - return nil if failed, self if ok"

%{  /* NOCONTEXT */

    FILE *f;
    char c;
    extern OBJ ErrorNumber;
    extern errno;
    int cnt;
    OBJ pos;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _readonly) {
            if (_isCharacter(aCharacter)) {
                c = _intVal(_CharacterInstPtr(aCharacter)->c_asciivalue);
    doWrite:
                f = MKFD(_INST(filePointer));

                if (_INST(unBuffered) == true) {
		    cnt = write(fileno(f), &c, 1);
		} else { 
		    if (_INST(mode) == _readwrite)
		        fseek(f, 0L, 1); /* needed in stdio */
                    cnt = fwrite(&c, 1, 1, f);
		}
                if (cnt == 1) {
                    if (_INST(unBuffered) == true) {
                        fflush(f);
                    }
                    pos = _INST(position);
                    if (pos != nil) {
                        _INST(position) = _MKSMALLINT(_intVal(pos) + 1);
                    }
                    RETURN ( self );
                }
                ErrorNumber = _MKSMALLINT(errno);
                RETURN ( nil );
            } else {
                if (_INST(binary) == true) {
                    if (_isSmallInteger(aCharacter)) {
                        c = _intVal(aCharacter);
                        goto doWrite;
                    }
                }
            }
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    self argumentMustBeCharacter
!

nextPutAll:aCollection
    "write all elements of the argument, aCollection"

%{  /* NOCONTEXT */

    FILE *f;
    unsigned char *cp;
    int len, cnt;
    extern OBJ ErrorNumber;
    extern errno;
    OBJ pos;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _readonly) {
            if (_isString(aCollection) || _isSymbol(aCollection)) {
                cp = _stringVal(aCollection);
                len = _stringSize(aCollection);
                f = MKFD(_INST(filePointer));

                if (_INST(unBuffered) == true) {
		    cnt = write(fileno(f), cp, len);
		} else { 
		    if (_INST(mode) == _readwrite)
		        fseek(f, 0L, 1); /* needed in stdio */
                    cnt = fwrite(cp, 1, len, f);
		}
                if (cnt == len) {
                    if (_INST(unBuffered) == true) {
                        fflush(f);
                    }
                    pos = _INST(position);
                    if (pos != nil) {
                        _INST(position) = _MKSMALLINT(_intVal(pos) + len);
                    }
                    RETURN ( self );
                }
                ErrorNumber = _MKSMALLINT(errno);
                RETURN ( nil );
            }
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].

    aCollection do:[:element |
        self nextPut:element
    ]
!

nextPut:aCollection from:start to:stop
    "write a range of elements of the argument, aCollection"

%{  /* NOCONTEXT */

    FILE *f;
    unsigned char *cp;
    int len, cnt, index1, index2;
    extern OBJ ErrorNumber;
    extern errno;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_isString(aCollection)
         && _isSmallInteger(start)
         && _isSmallInteger(stop)) {
            f = MKFD(_INST(filePointer));
            cp = _stringVal(aCollection);
            len = _stringSize(aCollection);
            index1 = _intVal(start);
            index2 = _intVal(stop);
            if ((index1 < 1) || (index2 > len) || (index2 < index1)) {
                RETURN ( self );
            }
            if (index2 > len)
                index2 = len;

            len = index2 - index1 + 1;
            if (_INST(unBuffered) == true) {
	        cnt = write(fileno(f), cp + index1 - 1, len);
	    } else { 
		if (_INST(mode) == _readwrite)
		    fseek(f, 0L, 1); /* needed in stdio */
                cnt = fwrite(cp + index1 - 1, 1, len, f);
	    }
            if (cnt == len) {
                if (_INST(unBuffered) == true) {
                    fflush(f);
                }
                if (_INST(position) != nil) {
                    _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + len);
                }
                RETURN ( self );
            }
            ErrorNumber = _MKSMALLINT(errno);
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].

    start to:stop do:[:index |
        self nextPut:(aCollection at:index)
    ]
!

cr
    "reimplemented for speed"

%{  /* NOCONTEXT */

    FILE *f;
    extern OBJ ErrorNumber;
    extern errno;
    extern int _immediateInterrupt;
    int cnt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _readonly) {
            f = MKFD(_INST(filePointer));

            if (_INST(unBuffered) == true) {
	        cnt = write(fileno(f), "\n", 1);
	    } else { 
		if (_INST(mode) == _readwrite)
		    fseek(f, 0L, 1); /* needed in stdio */
                cnt = fwrite("\n", 1, 1, f);
	    }
            if (cnt == 1) {
                if (_INST(unBuffered) == true) {
                    fflush(f);
                }
                if (_INST(position) != nil) {
                    _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + 1);
                }
                RETURN ( self );
            }
            ErrorNumber = _MKSMALLINT(errno);
            return ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self errorReadOnly
! !

!ExternalStream methodsFor:'more character I/O'!

nextWord
    "in text-mode:
         read the next word (i.e. up to non letter-or-digit).
         return a string containing those characters.
     in binary-mode:
         read two bytes (msb-first) and return the value as a 16-bit unsigned Integer
         (msb-first for compatibility with other smalltalks)"

%{  /* NOCONTEXT */
    extern int _immediateInterrupt;

    if (_INST(binary) == true) {
        if (_INST(filePointer) != nil) {
            if (_INST(mode) != _writeonly) {
                FILE *f;
                int hi, low;

                f = MKFD(_INST(filePointer));
                hi = getc(f);
                if (hi == EOF) {
                    RETURN ( nil );
                }
                low = getc(f);
                if (low == EOF) {
                    if (_INST(position) != nil) {
                        _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + 1);
                    }
                    RETURN ( _MKSMALLINT(hi & 0xFF) );
                }
                if (_INST(position) != nil) {
                    _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + 2);
                }
                RETURN ( _MKSMALLINT(((hi & 0xFF)<<8) | (low & 0xFF)) );
            }
        }
    }
%}
.
%{
    FILE *f;
    int len;
    char buffer[1024];
    int ch;
    int cnt = 0;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            /* text-mode */
            for (;;) {
                ch = getc(f);
                cnt++;

                if (ch >= ' ') break;
                if ((ch != ' ') && (ch != '\t') && (ch != '\r')
                 && (ch != '\n') && (ch != 0x0b)) break;
            }
            ungetc(ch, f);
            cnt--;

            len = 0;
            for (;;) {
                ch = getc(f);
                if (ch == EOF)
                    break;
                ch &= 0xFF;
                if (! (((ch >= 'a') && (ch <= 'z')) ||
                       ((ch >= 'A') && (ch <= 'Z')) ||
                       ((ch >= '0') && (ch <= '9')))) {
                    ungetc(ch, f);
                    break;
                }
                cnt++;
                buffer[len++] = ch;
                if (len >= sizeof(buffer)-1) {
                    /* emergency */
                    break;
                }
            }
            if (_INST(position) != nil) {
                _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + cnt);
            }
            buffer[len] = '\0';
            if (len != 0) {
                RETURN ( _MKSTRING(buffer COMMA_CON) );
            }
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self errorWriteOnly
!

nextWordPut:aNumber
    "only in binary-mode:
         write the argument, aNumber as two bytes (msb-first)
         (msb-first for compatibility with other smalltalks)"

%{  /* NOCONTEXT */

    int num;
    char bytes[2];
    FILE *f;
    extern OBJ ErrorNumber;
    extern errno;
    extern int _immediateInterrupt;

    if (_INST(binary) == true) {
        if (_INST(filePointer) != nil) {
            if (_INST(mode) != _readonly) {
                if (_isSmallInteger(aNumber)) {
                    num = _intVal(aNumber);
                    bytes[0] = (num >> 8) & 0xFF;
                    bytes[1] = num & 0xFF;

                    f = MKFD(_INST(filePointer));
                    if (fwrite(bytes, 1, 2, f) == 2) {
                        if (_INST(unBuffered) == true) {
                            fflush(f);
                        }
                        if (_INST(position) != nil) {
                            _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + 2);
                        }
                        RETURN ( self );
                    }
                    ErrorNumber = _MKSMALLINT(errno);
                    return ( nil );
                }
            }
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    binary ifFalse:[^ self errorNotBinary].
    self argumentMustBeInteger
!

nextLong
    "in binary-mode:
         read two bytes (msb-first) and return the value as a 16-bit unsigned Integer
         (msb-first for compatibility with other smalltalks)"

    |lo hi|

    binary ifFalse:[
        ^ self error:'method only valid in binary mode'
    ].
    hi := self nextWord.
    lo := self nextWord.
    ^ hi * 16r10000 + lo
!

nextLine
    "read the next line (characters up to newline).
     Return a string containing those characters excluding the newline.
     If the previous-to-last character is a cr, this is also removed,
     so its possible to read alien (i.e. ms-dos) text as well."

%{  /* NOCONTEXT */

    FILE *f;
    int len;
    char buffer[1024*16];
    extern int _immediateInterrupt;
    char *rslt;

    if (_INST(filePointer) != nil) {
        if (_INST(filePointer) != _writeonly) {
            f = MKFD(_INST(filePointer));
            _immediateInterrupt = 1;
            buffer[0] = 0;
            rslt = fgets(buffer, sizeof(buffer), f);
            _immediateInterrupt = 0;
            if (rslt != NULL) {
                len = strlen(buffer);
                if (_INST(position) != nil) {
                    _INST(position) = _MKSMALLINT(_intVal(_INST(position)) + len);
                }
                /* remove EOL character */
                if ((len != 0) && (buffer[len-1] == '\n')) {
                    buffer[--len] = '\0';
                }
                if ((len != 0) && (buffer[len-1] == '\r')) {
                    buffer[--len] = '\0';
                }
                RETURN ( _MKSTRING(buffer COMMA_CON) );
            }
        }
    }
%}
.
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ nil
!

nextPutLine:aString
    "write the characters in aString and append a newline"

%{  /* NOCONTEXT */

    FILE *f;
    int len, cnt;
    OBJ pos;
    char *s;
    extern OBJ ErrorNumber;
    extern errno;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _readonly) {
            if (_isString(aString)) {
                f = MKFD(_INST(filePointer));
                s = (char *) _stringVal(aString);
                len = _stringSize(aString);

                if (_INST(unBuffered) == true) {
		    cnt = write(fileno(f), s, len);
		} else { 
		    if (_INST(mode) == _readwrite)
		        fseek(f, 0L, 1); /* needed in stdio */
                    cnt = fwrite(s, 1, len, f);
		}
                if (cnt == len) {
                    if (_INST(unBuffered) == true) {
		        cnt = write(fileno(f), "\n", 1);
		    } else { 
                        cnt = fwrite("\n", 1, 1, f);
		    }
                    if (cnt == 1) {
                        pos = _INST(position);
                        if (pos != nil) {
                            _INST(position) = _MKSMALLINT(_intVal(pos)+len+1);
                        }
                        RETURN ( self );
                    }
                }
                ErrorNumber = _MKSMALLINT(errno);
                RETURN ( nil );
            }
        }
    }
%}
.
    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self argumentMustBeString
!

nextPutLinesFrom:aStream upToLineStartingWith:aStringOrNil
    "used to copy large files
     - read from aStream up to and including a line starting with aStringOrNil
     and append it to self. If aStringOrNil is nil or not matched,
     copy preceeds to the end"

    |srcFilePointer|

    (mode == #readonly) ifTrue:[^ self errorReadOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    srcFilePointer := aStream filePointer.
    srcFilePointer isNil ifTrue:[^ aStream errorNotOpen].
%{
    FILE *dst, *src;
    char *matchString;
    int matchLen = 0;
    char buffer[1024*16];
    extern int _immediateInterrupt;

    if (_isSmallInteger(srcFilePointer)) {
        if ((aStringOrNil == nil)
         || _isString(aStringOrNil)) {
            if (aStringOrNil != nil) {
                matchString = (char *) _stringVal(aStringOrNil);
                matchLen = _stringSize(aStringOrNil);
            }
            dst = MKFD(_INST(filePointer));
            src = (FILE *)_intVal(srcFilePointer);
            for (;;) {
                if (fgets(buffer, sizeof(buffer), src) == NULL) break;
                if (fputs(buffer, dst) == EOF) break;
                if (matchLen) {
                    if (strncmp(matchString, buffer, matchLen) == 0) 
                        break;
                }
            }
            if (_INST(unBuffered) == true) {
                fflush(dst);
            }
            _INST(position) = nil;
            RETURN ( self );
        }
    }
%}
.
    ^ self primitiveFailed
!

peekForLineStartingWith:aString
    "read ahead for next line starting with aString;
     return the line-string if found, nil otherwise..
     do not advance position i.e. nextLine will reread this line"

    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
%{
    FILE *f;
    int l;
    char buffer[1024*10];
    char *cp;
    char *matchString;
    int  firstpos, lastpos;
    extern int _immediateInterrupt;

    if (_isString(aString)) {
        matchString = (char *) _stringVal(aString);
        l = _stringSize(aString);

        f = MKFD(_INST(filePointer));
        firstpos = ftell(f);
        for (;;) {
            lastpos = ftell(f);
            _immediateInterrupt = 1;
            cp = fgets(buffer, sizeof(buffer), f);
            _immediateInterrupt = 0;
            if (cp == NULL) {
                fseek(f, firstpos, 0);
                RETURN ( nil );
            }
            if (strncmp(cp, matchString, l) == 0) {
                fseek(f, lastpos, 0);
                break;
            }
        }
        /* remove EOL character */
        cp = buffer;
        while (*cp && (*cp != '\n')) cp++;
        *cp = '\0';
        RETURN ( _MKSTRING(buffer COMMA_CON) );
    }
%}
.
    self argumentMustBeString
!

peekForLineStartingWithAny:aCollectionOfStrings
    "read ahead for next line starting with any of aCollectionOfStrings;
     return the index in aCollection if found, nil otherwise..
     If no match, do not change position; otherwise advance right before the
     matched line so that nextLine will return this line."

    |line startPos linePos index|

    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    filePointer isNil ifTrue:[^ self errorNotOpen].
    startPos := self position.
    [self atEnd] whileFalse:[
        linePos := self position.
        line := self nextLine.
        index := 1.
        aCollectionOfStrings do:[:prefix |
            (line startsWith:prefix) ifTrue:[
                self position:linePos.
                ^ index
            ].
            index := index + 1
        ]
    ].
    self position:startPos.
    ^ nil
! !

!ExternalStream methodsFor:'testing'!

atEnd
    "return true, if position is at end"

%{  /* NOCONTEXT */

    FILE *f;

    if (_INST(filePointer) != nil) {
        f = MKFD(_INST(filePointer));
        RETURN ( feof(f) ? true : false );
    }
%}
.
    self errorNotOpen
! !

!ExternalStream methodsFor:'closing'!

close
    "close the stream - tell operating system"

    filePointer isNil ifTrue:[^ self].
    lobby unregister:self.
    self closeFile.
    filePointer := nil
! !

!ExternalStream methodsFor:'reimplemented for speed'!

peekFor:aCharacter
    "return true and move past if next == something.
     - reimplemented for speed"

%{  /* NOCONTEXT */

    FILE *f;
    int c;
    int peekvalue;
    extern int _immediateInterrupt;

    if (_isCharacter(aCharacter)) {
        if (_INST(filePointer) != nil) {
            peekvalue = _intVal(_CharacterInstPtr(aCharacter)->c_asciivalue);
            f = MKFD(_INST(filePointer));
            c = getc(f);
            if (c == peekvalue) {
                RETURN ( true );
            }
            ungetc(c, f);
            RETURN ( false );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    ^ super peekFor:aCharacter
!

skipLine
    "read the next line (characters up to newline) skip only;
     return nil if EOF reached"

%{  /* NOCONTEXT */

    FILE *f;
    char buffer[1024*10];
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            if (fgets(buffer, sizeof(buffer), f) != NULL) {
                RETURN ( self );
            }
            RETURN ( nil );
        }
    }
%}
.
    filePointer isNil ifTrue:[^ self errorNotOpen].
    self errorWriteOnly
!

skipToAll:aString
    "skip for the sequence given by the argument, aCollection;
     return nil if not found, self otherwise. On a successful match, next read
     will return characters of aString."

    |oldPos buffer l first idx|

    (aString isKindOf:String) ifTrue:[
        oldPos := self position.
        l := aString size.
        first := aString at:1.
        buffer := String new:l.
        [true] whileTrue:[
            (self nextBytes:l into:buffer) == l ifFalse:[
                self position:oldPos.
                ^ nil
            ].
            buffer = aString ifTrue:[
                self position:(self position - l).
                ^ self
            ].
            idx := buffer indexOf:first startingAt:2.
            idx == 0 ifFalse:[
                self position:(self position - l + idx - 1)
            ]
        ]
    ].
    ^ super skipFor:aString
!

skipSeparators
    "skip all whitespace; next will return next non-white-space character
     or nil if endOfFile reached.
     - reimplemented for speed"

%{  /* NOCONTEXT */

    FILE *f;
    REGISTER int c;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            while (1) {
                if (feof(f)) {
                    RETURN ( nil );
                }
                c = getc(f);
                if (c < 0) {
                    RETURN ( nil );
                }
                switch (c) {
                    case ' ':
                    case '\t':
                    case '\n':
                    case '\r':
                    case '\b':
                    case '\014':
                        break;
                    default:
                        ungetc(c, f);
                        RETURN ( _MKCHARACTER(c & 0xFF) );
                }
            }
        }
    }
%}
.
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    self errorNotOpen
!

skipSeparatorsExceptCR
    "skip all whitespace but no newlines;
     next will return next non-white-space character
     or nil if endOfFile reached.
     - reimplemented for speed"

%{  /* NOCONTEXT */

    FILE *f;
    int c;
    extern int _immediateInterrupt;

    if (_INST(filePointer) != nil) {
        if (_INST(mode) != _writeonly) {
            f = MKFD(_INST(filePointer));
            while (1) {
                if (feof(f)) {
                    RETURN ( nil );
                }
                c = getc(f);
                if (c < 0) {
                    RETURN ( nil );
                }
                switch (c) {
                    case ' ':
                    case '\t':
                    case '\b':
                        break;
                    default:
                        ungetc(c, f);
                        RETURN ( _MKCHARACTER(c & 0xFF) );
                }
            }
        }
    }
%}
.
    (mode == #writeonly) ifTrue:[^ self errorWriteOnly].
    self errorNotOpen
!

nextChunk
    "return the next chunk, i.e. all characters up to the next
     non-doubled exclamation mark; undouble doubled exclamation marks.
     - reimplemented for speed"
    |retVal|

    filePointer isNil ifTrue:[
        ^ self errorNotOpen
    ].
%{
    FILE *f;
    int done = 0;
    REGISTER int c;
    unsigned char peekC;
    char *buffer, *newBuffer;
    REGISTER int index;
    int currSize;
    int inComment, inString, inPrimitive = 0;
    extern int _immediateInterrupt;

    f = MKFD(_INST(filePointer));
    /*
     * skip spaces
     */
    while (! done) {
        if (feof(f)) {
            RETURN ( nil );
        }
        c = getc(f);
        switch (c) {
            case ' ':
            case '\t':
            case '\n':
            case '\r':
            case '\b':
            case '\014':
                break;

            case EOF:
                RETURN ( nil );

            default:
                ungetc(c, f);
                done = 1;
                break;
        }
    }

    /*
     * read chunk into a buffer
     */
    buffer = (char *)malloc(3000);
    currSize = 3000;
    index = 0;
    while (! feof(f)) {
        /* do we have to resize the buffer ? */
        if ((index+2) >= currSize) {
            newBuffer = (char *)malloc(currSize * 2);
            bcopy(buffer, newBuffer, index);
            free(buffer);
            buffer = newBuffer;
            currSize = currSize * 2;
        }
        c = getc(f);
        if (c == '%') {
            peekC = getc(f);
            ungetc(peekC, f);
            if (peekC == '{') {
                inPrimitive++;
            } else if (peekC == '}') {
                inPrimitive--;
            }
        } else {
            if (! inPrimitive) {
                if (c == '!') {
                    c = getc(f);
                    if (c != '!') {
                        ungetc(c, f);
                        break;
                    }
                }
            }
        }
        if (c == EOF) break;
        buffer[index++] = c;
    }
    buffer[index] = '\0';
    /*
     * make it a string
     */
    retVal = _MKSTRING(buffer COMMA_CON);
    free(buffer);
%}
.
    ^ retVal
! !