ExtStream.st
changeset 1 a27a279701f8
child 2 6526dde5f3ac
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ExtStream.st	Fri Jul 16 11:39:45 1993 +0200
@@ -0,0 +1,1437 @@
+"
+ 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
+! !