SystemOrganization addCategory: #Generator! Stream subclass: #Generator instanceVariableNames: 'block next continue home' classVariableNames: '' poolDictionaries: '' category: 'Generator'! !Generator commentStamp: 'lr 2/16/2010 13:48' prior: 0! A Generator transforms callback interfaces into stream interfaces. When a producer algorithm provide results as callbacks (blocks) and a consumer algorithm expects streamable input, a Generator transforms one into the other. The method #primesUpTo:do: is such an example: | generator | generator := Generator on: [ :gen | Integer primesUpTo: 100 do: [ :each | gen yield: each ] ]. [ generator atEnd ] whileFalse: [ Transcript show: generator next; cr ]. Another example is FileDirectory>>withAllFilesDo:andDirectoriesDo:. We can get the first 10 files by creating a generator like this: | generator | generator := Generator on: [ :gen | FileDirectory default withAllFilesDo: gen andDirectoriesDo: [ :dir | ] ]. generator next: 10 Note that the generator understands the message #value: as a synonym to #yield:, thus the generator itself can be used as a replacement for the block. Instance Variables block: The block associated with the generator. continue: The activation context to return to. home: The home (root) context of the activated block. next: The next object to return from the Generator.! !Generator class methodsFor: 'instance-creation' stamp: 'lr 1/8/2009 15:54'! on: aBlock ^ self basicNew initializeOn: aBlock! ! !Generator methodsFor: 'testing' stamp: 'ar 2/10/2010 21:00'! atEnd "Answer whether the receiver can access any more objects." ^ continue isNil! ! !Generator methodsFor: 'accessing' stamp: 'lr 2/16/2010 13:35'! close "Close the receiving generator and unwind its ensure-blocks." continue isNil ifFalse: [ continue unwindTo: home ]. continue := block := next := nil! ! !Generator methodsFor: 'accessing' stamp: 'lr 4/26/2009 11:50'! contents "Answer the contents of this generator. Do not call this method on infinite generators." | stream | stream := (Array new: 10) writeStream. [ self atEnd ] whileFalse: [ stream nextPut: self next ]. ^ stream contents! ! !Generator methodsFor: 'private' stamp: 'ar 2/10/2010 20:46'! fork | result | home := thisContext. block reentrant value: self. thisContext swapSender: continue. result := next. continue := next := home := nil. ^ result! ! !Generator methodsFor: 'initialization' stamp: 'lr 1/8/2009 16:18'! initializeOn: aBlock block := aBlock. self reset! ! !Generator methodsFor: 'accessing' stamp: 'lr 2/16/2010 13:49'! next "Generate and answer the next object in the receiver." self atEnd ifTrue: [ ^ nil ]. home swapSender: thisContext sender. continue := thisContext swapSender: continue! ! !Generator methodsFor: 'accessing' stamp: 'lr 2/10/2010 09:16'! nextPut: anObject "Add anObject into the generator. A synonym to #yield: and value:." | previous | previous := next. next := anObject. continue := thisContext swapSender: continue. ^ previous! ! !Generator methodsFor: 'accessing' stamp: 'lr 2/10/2010 09:16'! peek "Answer the upcoming object of the receiver." ^ next! ! !Generator methodsFor: 'printing' stamp: 'lr 1/8/2009 16:21'! printOn: aStream aStream nextPutAll: self class name; nextPutAll: ' on: '; print: block! ! !Generator methodsFor: 'public' stamp: 'lr 2/16/2010 13:24'! reset "Reset the generator to start it over." continue isNil ifFalse: [ continue unwindTo: home ]. next := nil. continue := thisContext. [ self fork ] value! ! !Generator methodsFor: 'accessing' stamp: 'lr 2/10/2010 09:16'! size "A generator does not know its size." ^ self shouldNotImplement! ! !Generator methodsFor: 'public' stamp: 'lr 2/16/2010 13:50'! value: anObject "Allows passing generators as arguments to methods expecting blocks. A synonym for #yield: / #nextPut:." ^ self nextPut: anObject! ! !Generator methodsFor: 'public' stamp: 'lr 2/16/2010 13:50'! yield: anObject "Yield the next value to the consumer of the generator. A synonym for #nextPut:." ^ self nextPut: anObject! ! TestCase subclass: #GeneratorTest instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Generator'! !GeneratorTest class methodsFor: 'accessing' stamp: 'lr 2/10/2010 08:34'! packageNamesUnderTest ^ #('Generator')! ! !GeneratorTest methodsFor: 'generators' stamp: 'lr 1/8/2009 16:29'! fibonacciSequence "Yields an infinite sequence of fibonacci numbers." ^ Generator on: [ :generator | | a b | a := 0. b := 1. [ a := b + (b := a). generator yield: a ] repeat ]! ! !GeneratorTest methodsFor: 'generators' stamp: 'lr 2/16/2010 13:26'! numbersBetween: aStartInteger and: aStopInteger "Yields the numbers between aStartInteger and aStopInteger." ^ Generator on: [ :generator | aStartInteger to: aStopInteger do: [ :value | generator yield: value ] ]! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 1/8/2009 16:33'! testAtEnd | generator | generator := self numbersBetween: 1 and: 3. self deny: generator atEnd. generator next. self deny: generator atEnd. generator next. self deny: generator atEnd. generator next. self assert: generator atEnd! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 2/10/2010 09:18'! testClose | generator doEnsure notEnsure | doEnsure := notEnsure := 0. [ generator := Generator on: [ :g | [ g yield: 1; yield: 2 ] ensure: [ doEnsure := doEnsure + 1 ] ]. self assert: doEnsure = 0; assert: notEnsure = 0. self assert: generator peek = 1. self assert: doEnsure = 0; assert: notEnsure = 0. generator close. self assert: doEnsure = 1; assert: notEnsure = 0 ] ensure: [ notEnsure := notEnsure + 1 ]. self assert: doEnsure = 1; assert: notEnsure = 1! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 4/26/2009 11:51'! testContents | generator | generator := self numbersBetween: 1 and: 3. self assert: generator contents = #(1 2 3)! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 1/8/2009 16:45'! testEmpty | generator | generator := Generator on: [ :g | ]. self assert: generator atEnd. self assert: generator peek isNil. self assert: generator next isNil! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 2/10/2010 09:02'! testEnsure | generator | generator := Generator on: [ :g | [ g yield: 1; yield: 2 ] ensure: [ g yield: 3 ] ]. self assert: generator upToEnd asArray = #( 1 2 3 )! ! !GeneratorTest methodsFor: 'testing' stamp: 'ar 2/10/2010 21:01'! testErrorPropagation "Ensure that errors in the generator block are properly propagated" | generator | self shouldnt:[generator := Generator on: [ :g | g yield: 1. g error: 'yo']] raise: Error. self should:[generator next] raise: Error. ! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 1/8/2009 16:50'! testFibonacci | generator | generator := self fibonacciSequence. self assert: (generator next: 10) asArray = #( 1 1 2 3 5 8 13 21 34 55 )! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 1/8/2009 16:33'! testNext | generator | generator := self numbersBetween: 1 and: 3. self assert: generator next = 1. self assert: generator next = 2. self assert: generator next = 3. self assert: generator next isNil! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 1/8/2009 16:45'! testPeek | generator | generator := self numbersBetween: 1 and: 3. self assert: generator peek = 1. self assert: generator peek = 1. generator next. self assert: generator peek = 2! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 2/16/2010 13:57'! testPrintString | generator | generator := self fibonacciSequence. self assert: generator printString isString.! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 2/16/2010 13:25'! testReset | generator | generator := self numbersBetween: 1 and: 3. self assert: generator next = 1. self assert: generator next = 2. generator reset. self assert: generator next = 1. self assert: generator next = 2. self assert: generator next = 3. self assert: generator next isNil. generator reset. self assert: generator next = 1! ! !GeneratorTest methodsFor: 'testing' stamp: 'ar 2/10/2010 21:03'! testResetUnwind "Just like close, just using reset" | generator doEnsure notEnsure | doEnsure := notEnsure := 0. [ generator := Generator on: [ :g | [ g yield: 1; yield: 2 ] ensure: [ doEnsure := doEnsure + 1 ] ]. self assert: doEnsure = 0; assert: notEnsure = 0. self assert: generator peek = 1. self assert: doEnsure = 0; assert: notEnsure = 0. generator reset. self assert: doEnsure = 1; assert: notEnsure = 0 ] ensure: [ notEnsure := notEnsure + 1 ]. self assert: doEnsure = 1; assert: notEnsure = 1! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 1/8/2009 16:46'! testSimple | generator | generator := Generator on: [ :g | g yield: 1; yield: 2 ]. self assert: generator upToEnd asArray = #( 1 2 )! ! !GeneratorTest methodsFor: 'testing' stamp: 'lr 2/16/2010 13:56'! testValue | values generator | values := Set withAll: (1 to: 100). generator := Generator on: [ :gen | values do: gen ]. [ generator atEnd ] whileFalse: [ values remove: generator next ]. self assert: values isEmpty ! !