!=========================================================================
! Copyright (C) VMware, Inc. 1986-2011.  All Rights Reserved.
!
! $Id: positio.gs,v 1.7.2.5 2008-03-04 19:03:19 dhenrich Exp $
!
! Superclass Hierarchy:
!   PositionableStream, Stream, Object.
!
!=========================================================================

removeallmethods PositionableStream
removeallclassmethods PositionableStream

category: 'For Documentation Installation only'
classmethod: PositionableStream
installDocumentation

| doc txt |
doc := GsClassDocumentation newForClass: self.

txt := (GsDocText new) details:
'PositionableStream is an abstract superclass that provides additional protocol
 appropriate to Streams whose objects are externally named by indices.
 Concrete subclasses are ReadStream and WriteStream.'.
doc documentClassWith: txt.

txt := (GsDocText new) details:
'A SequenceableCollection; the sequence of objects that the receiver may
 access.'.
doc documentInstVar: #itsCollection with: txt.

txt := (GsDocText new) details:
'A SmallInteger; the current position reference for accessing the collection.'.
doc documentInstVar: #position with: txt.

self description: doc.
%

category: 'Instance Creation'
classmethod: PositionableStream
on: aCollection

"Returns an instance of the receiver that can stream over the elements of
 aCollection."

| newStream |

newStream := self _basicNew.
newStream _initStreamWith: aCollection.
^ newStream
%

category: 'Accessing'
method: PositionableStream
contents

"Returns the Collection associated with the receiver (that is,
 the sequence of objects that the receiver may access)."

^itsCollection
%

category: 'Accessing'
method: PositionableStream
peek

"Returns the next element in the collection, but does not alter the current
 position reference.  If the receiver is at the end of the collection, returns
 nil."

self atEnd ifTrue: [^nil].
^itsCollection at: position
%

category: 'Accessing'
method: PositionableStream
skip: amount

"Sets the receiver's position to position+amount."

self position: self position + amount
%

category: 'Testing'
method: PositionableStream
atEnd

"Returns true if the receiver cannot access any more objects, false if it can."

^ position > itsCollection size
%

category: 'Testing'
method: PositionableStream
isEmpty

"Returns true if the collection that the receiver accesses contains
 no elements; otherwise returns false."

^ itsCollection size == 0
%

category: 'Positioning'
method: PositionableStream
position

"Returns the receiver's current position reference for accessing the sequence
 of objects.  The position is actually the next element of the collection to be
 read or written; the position is incremented by each read or write.  In
 general, to start reading or writing at the nth element of a collection, the
 position must be set to n."

^position
%

category: 'Positioning'
method: PositionableStream
position: anInteger

"Sets the receiver's current position reference for accessing the collection to
 be anInteger.  If anInteger is not within the bounds of the collection,
 generates an error."

(anInteger > 0) & (anInteger <= (itsCollection size + 1))
   ifTrue: [position := anInteger]
   ifFalse: [self _positionError: anInteger]
%

category: 'Positioning'
method: PositionableStream
_positionError: anInteger

"Returns an error message that anInteger is out of bounds of the Collection."

^ self _error: #rtErrBadStreamPosition args: #[anInteger]
%

category: 'Positioning'
method: PositionableStream
reset

"Sets the receiver's position to the beginning of the sequence of objects."

position := 1
%

category: 'Positioning'
method: PositionableStream
_initStreamWith: aCollection

"Initialize the receiver's 'itsCollection' instance variable to be
 aCollection."

"gemstone64, explicit constraint enforcement"
(aCollection isKindOf: SequenceableCollection) ifFalse:[
   ^ self _error: #objErrConstraintViolation
     args:#[ aCollection, SequenceableCollection, aCollection class ]. 
].

itsCollection := aCollection.
position := 1.
%

category: 'Accessing'
method: PositionableStream
backup

"Backs up the receiver one position."

self position: self position - 1
%

category: 'Accessing'
method: PositionableStream
nextWord

"Assume that the receiver's collection is a kind of String.  Returns the
 next word in the string or nil if there is no next word."

| result |
result := itsCollection species new.
self skipSeparators.
[self atEnd not _and: [self peek isSeparator not]] whileTrue: [
  result add: self next
].
result size == 0
    ifTrue: [^nil]
    ifFalse: [^result]
%

! fix bug 10257
category: 'Accessing'
method: PositionableStream
peekWord

"Assume that the receiver's collection is a kind of String.  Returns the
 next word in the string without moving the receiver's position."

| result pos |
pos := self position.
result := self nextWord .
self position: pos.
^result
%

category: 'Accessing'
method: PositionableStream
skipAny: chars

"Skip past all Characters in chars.  Returns the number of Characters skipped."

| skipped |
skipped := 0.
[self atEnd not _and: [(chars includesIdentical: self peek)]] whileTrue: [
  self next.
  skipped := skipped + 1.
].
^skipped
%

category: 'Accessing'
method: PositionableStream
skipSeparators

"Skip any objects immediately next in the stream that respond true to
 isSeparator."

[self atEnd not _and: [self peek isSeparator]] whileTrue: [ self next ]
%


category: 'Accessing'
method: PositionableStream
throughAll: matchCollection

"Returns a collection of objects from the receiver up to and including the
sequence of objects in the argument 'matchCollection', leaving the stream
positioned after the sequence.  If the sequence of objects is not found, this
returns the remaining contents of the receiver and leaves me positioned
at my end."

| numMatched numToMatch result  |

numMatched := 0.
result := itsCollection species new.
numToMatch := matchCollection size.
[self atEnd _or: [numMatched = numToMatch]]
     whileFalse:
           [self next = (matchCollection at: numMatched + 1)
                ifTrue: [numMatched := numMatched + 1]
          ifFalse: [position := position - numMatched - 1.
                        result add: self next.
                        numMatched := 0]
].

"add matched or partially matched chars"
position := position - numMatched.
numMatched timesRepeat: [result add: self next].

^ result.
%

category: 'Accessing'
method: PositionableStream
upTo: anObject

"Returns all objects up to the given value or the end of the stream."

| result |
result := itsCollection species new.
[self atEnd not _and: [self peek ~= anObject]] whileTrue: [
  result add: self next
].
self atEnd not ifTrue: [
  self next
].
^result
%

category: 'Accessing'
method: PositionableStream
upTo: anObject do: aBlock

"Sends each object encountered to the given block until the end of stream
 or the given value is encountered.  Returns the receiver."

[self atEnd not _and: [self peek ~= anObject]] whileTrue: [
  aBlock value: self next
].
self atEnd ifTrue: [
  self next
].
%

category: 'Accessing'
method: PositionableStream
upToAll: matchCollection 

"Returns a collection of objects from the receiver up to, but not including,
 the sequence of objects in the argument 'matchCollection', leaving the stream
 positioned to read the sequence.  If the sequence of objects is not found,
 this returns the remaining contents of the receiver and leaves the stream 
 positioned at the end." 

| numMatched numToMatch result | 

numMatched := 0. 
result := itsCollection species new. 
numToMatch := matchCollection size. 
[self atEnd _or: [numMatched == numToMatch]] 
  whileFalse: 
    [self next = (matchCollection at: numMatched + 1) 
      ifTrue: [numMatched := numMatched + 1] 
      ifFalse: [position := position - numMatched - 1. 
    result add: self next. 
    numMatched := 0] 
    ]. 
"Position before any partial or complete match we might have found." 
position := position - numMatched. 

"If the match was not complete, must add any partially matched chars." 
numMatched ~~ numToMatch 
  ifTrue: [numMatched timesRepeat: [result add: self next]]. 

^ result. 
%

category: 'Accessing'
method: PositionableStream
upToAny: objects

"Returns all objects up to one of the given collection of objects or the end
 of the stream."

| result |
result := itsCollection species new.
[self atEnd not _and: [(objects includesIdentical: self peek) not]] whileTrue: [
  result add: self next
].
self atEnd not ifTrue: [
  self next
].
^result
%

category: 'Accessing'
method: PositionableStream
upToAny: objects do: aBlock

"Send each Character encountered to aBlock until the end of stream
 or one of the given Characters is encountered."

[self atEnd not _and: [(objects includesIdentical: self peek) not]] whileTrue: [
  aBlock value: self next
].
self atEnd not ifTrue: [ self next ].
%

! fix 34786 , upToEnd should advance position to one past end 
category: 'Accessing'
method: PositionableStream
upToEnd

"Returns all Characters from the current position to the end of the stream."

| result end |
end := itsCollection size .
result := itsCollection copyFrom: position to: end .
position :=  end + 1 .
^ result
%

category: 'Accessing'
method: PositionableStream
_collection

"Returns the collection of the receiver."

^itsCollection
%

category: 'Accessing'
method: PositionableStream
next: count

"Returns the next count elements in the receiver's collection."

| result |
result := itsCollection species new.
count timesRepeat: [ result add: self next ].
^result
%

category: 'Accessing'
method: PositionableStream
next: count into: anObject

"Stores the next count elements in the receiver's collection into the
 given object.  Returns the argument anObject."

| idx |
idx := 1.
count timesRepeat: [ anObject at: idx put: self next. idx := idx + 1 ].
^anObject
%

category: 'Accessing'
method: PositionableStream
_next: count basicInto: anObject

"Stores the next count basic elements in the receiver's collection into the
 given object.  Returns the argument anObject."

| idx |
idx := 1.
count timesRepeat: [ 
   anObject _basicAt: idx put: self next asciiValue. 
   idx := idx + 1 
   ].
^anObject
%

category: 'Accessing'
method: PositionableStream
peek2

"Peeks at the second incoming object."

| pos result |
pos := self position.
result := self next; next.
self position: pos.
^result
%

