--- /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
+! !