" NAME CaseStatement AUTHOR cgreuter@calum.csclub.uwaterloo.ca (Chris Reuter) URL (none) FUNCTION Control structure similar to C's switch/case construct. KEYWORDS case switch ST-VERSIONS Squeak PREREQUISITES (none) CONFLICTS (none known) DISTRIBUTION world VERSION 0.0.1 DATE 03-Sep-98 SUMMARY This is my trivial Squeak port of Paul Bauman'scase statements as published in the July/August1997 issue of The Smalltalk Report.It seems to work correctly, but your mileagemay vary. If you do find and fix a bug, pleasesend me a copy of the changes.See the class comment for documentation. Chris Reuter "! 'From Squeak 2.0 of May 22, 1998 on 17 July 1998 at 7:34:18 pm'! Object subclass: #Case instanceVariableNames: 'satisfied response criterion ' classVariableNames: '' poolDictionaries: '' category: 'Case Statement'! !Object methodsFor: 'case statement'! switch "Answer a new instance of Case with the receiver as the criterion to test against." ^Case for: self! ! !Case commentStamp: 'cgr 7/17/1998 19:34' prior: 0! Case class add a robust case statement implementation to Smalltalk. The implemenation does NOT require any compiler changes. The code is from the article, "Smalltalk Case Statements" by Paul Baumann in the July/August 1997 issue of The Smalltalk Report. Baumann's article illustrates the ease of providing support for a case statement, gives examples of their use, and explains why the example case statement is appropiate. Comments taken from Baumann's article on the factors of when using a case statement is good. These factors are: (a) tests must be performed in sequence; (b) the conditional test is complex; (c) the test conditions are not consistent; (d) the criterion isn't easily represented by the application classes; (e) the test conditions depend on state of the criterion rather than the class or behavior of the criterion; (f) ease of maintenance is more important than performance; (g) there are more than three possible results. Various class examples illustrate some factor or factors as stated by Baumann. InstanceVariables: satisfied response result of execBlock value where execBlock is from one of the following: caseIs: testObject then: execBlock caseIsAny: testCollection then: execBlock isFalse: testBlock then: execBlock isTrue: testBlock then: execBlock default: execBlock criterion for which an instance of Case was made. Case class >> for: anObject which is sent by Object >> switch ClassVariables: NONE ! !Case methodsFor: 'public'! case: oneArgTestBlock process: execBlock "Idential to #case:then: except that the receiver will not be satisfied, and processing will fall through to the next statement." self isSatisfied ifFalse: [ (oneArgTestBlock value: criterion) ifTrue: [ self response: execBlock value. satisfied := false. ]. ]. ^self response! ! !Case methodsFor: 'public'! case: oneArgTestBlock then: execBlock "oneArgTestBlock must return a boolean value when passed the criterion of the receiver." self isSatisfied ifFalse: [ (oneArgTestBlock value: criterion) ifTrue: [ self response: execBlock value. satisfied := true. ]. ]. ^self response! ! !Case methodsFor: 'public'! caseIs: testObject process: execBlock "Idential to #caseIs:then: except that the receiver will not be satisfied, and processing will fall through to the next statement." ^self case: [:caseCriterion| caseCriterion = testObject ] process: execBlock! ! !Case methodsFor: 'public'! caseIs: testObject then: execBlock "If testObject equals the criterion of the receiver, then satisfy the receiver and answer the value of execBlock." ^self case: [:caseCriterion| caseCriterion = testObject ] then: execBlock! ! !Case methodsFor: 'public'! caseIsAny: testCollection process: execBlock "Idential to #caseIsAny:then: except that the receiver will not be satisfied, and processing will fall through to the next statement." ^self case: [:caseCriterion| testCollection includes: caseCriterion ] process: execBlock! ! !Case methodsFor: 'public'! caseIsAny: testCollection then: execBlock "If testCollection includes the criterion of the receiver, then satisfy the receiver and answer the value of execBlock." ^self case: [:caseCriterion| testCollection includes: caseCriterion ] then: execBlock! ! !Case methodsFor: 'public'! default: execBlock "This method sets the response without signaling the receiver has been satisfied. Note that the value of satisfied can be nil, true, or false; and that only a value of nil indicates that no case was run." satisfied isNil ifTrue: [self response: execBlock value]. ^self response! ! !Case methodsFor: 'public'! isFalse: testBlock process: execBlock "Idential to #isFalse:then: except that the receiver will not be satisfied, and processing will fall through to the next statement." ^self case: [:caseCriterion| testBlock value not ] process: execBlock! ! !Case methodsFor: 'public'! isFalse: testBlock then: execBlock "testBlock is a zero argument block that when evaluates false, execBlock will be executed. The criterion of the receiver is ignored." ^self case: [:caseCriterion| testBlock value not ] then: execBlock! ! !Case methodsFor: 'public'! isTrue: testBlock process: execBlock "Idential to #isTrue:then: except that the receiver will not be satisfied, and processing will fall through to the next statement." ^self case: [:caseCriterion| testBlock value ] process: execBlock! ! !Case methodsFor: 'public'! isTrue: testBlock then: execBlock "testBlock is a zero argument block that when evaluates true, execBlock will be executed. The criterion of the receiver is ignored." ^self case: [:caseCriterion| testBlock value ] then: execBlock! ! !Case methodsFor: 'private'! criterion: anObject "Set the standard of judging a true case." criterion := anObject! ! !Case methodsFor: 'private'! isSatisfied "satisfied may be nil, false, or true." ^satisfied == true! ! !Case methodsFor: 'private'! response "Answer the last response for a processed selection, or nil if no selections were processed." ^response! ! !Case methodsFor: 'private'! response: newResponse "Set the value that will be returned for the receiver." response := newResponse! ! !Case class methodsFor: 'documentation'! aaClassComment "Class comment located here so it does not get lost when filed in to Digitalk Smalltalk. Case class add a robust case statement implementation to Smalltalk. The implemenation does NOT require any complier changes. The code is from the article, 'Smalltalk Case Statements' by Paul Baumann in the July/August 1997 issue of The Smalltalk Report. Baumann's article illustrates the ease of providing support for a case statement, gives examples of their use, and explains why the example case statement is appropiate. Comments taken from Baumann's article on the factors of when using a case statement is good. These factors are: (a) tests must be performed in sequence; (b) the conditional test is complex; (c) the test conditions are not consistent; (d) the criterioan isn't easily represented by the application classes; (e) the test conditions depend on state of the criterion reather than the class or behavior of the criterion; (f) ease of maintenance is more important than performance; (g) there are more than three possible results. Various class examples illustrate some factor or factors as stated by Baumann. InstanceVariables: satisfied response result of execBlock value where execBlock is from one of the following: caseIs: testObject then: execBlock caseIsAny: testCollection then: execBlock isFalse: testBlock then: execBlock isTrue: testBlock then: execBlock default: execBlock criterion for which an instance of Case was made using Case class >> for: anObject which is send by Object >> switch ClassVariables: NONE "! ! !Case class methodsFor: 'documentation'! programersGuide " A simple example is given in #vendLargestAvailableCurrency: unVendedCurrenyValue The first part of the method after comments is: | hasHundred hasFifty hasTwenty hasTen | hasHundred := true. hasFifty := true. hasTwenty := true. hasTen := true. ^unVendedCurrenyValue switch . . . cascaded case keyword messages . . . The activation of the Case statements is via the unary message #switch. #switch creates an instance of Case with the receiver (unVendedCurrenyValue) stored in the instance variable, criterion. The cascaded case keyword messages are then send to the instance of Case. The method is exited when the first #case: block that is 'satisfied' after processing the #then: block. If no case: blocks were satisfied then the default: block is executed. See Case instance protocol 'public' for the available types of Case keyword messages. The meanings of Case keyword and arguments are as follows: default: aBlock - where aBlock is a zero argument block which is executed if none of the preceeding case statements criterion tests were satisified. A then: keyword means exit the case cascade after evaluating execBlock. A process: keyword means evaluate the execBlock and continue with the next case statement. oneArgTestBlock - a one argument block which will be sent the value of criterion when evaluated. Block MUST answer a boolean indicating the result of the test. execBlock - a zero argument block to evaluate when the criterion test result is true (satisfied). testObject - test the testObject equal (=) the criterion. testCollection - A collection of obects which the criterion must be equal(=) to one of them. testBlock - a one argument block that must answer a boolean. The criterion is send to testBlock. Case Statements that will test against the criterion. case: oneArgTestBlock then: execBlock case: oneArgTestBlock process: execBlock caseIs: testObject then: execBlock caseIs: testObject process: execBlock caseIsAny: testCollection then: execBlock caseIsAny: testCollection process: execBlock For #caseIs: testObject, testObject is tested to be equal the criterion. For #caseIsAny: testCollection, testCollection includes: criterion Case Statements that will NOT test against the criterion. isFalse: testBlock then: execBlock isFalse: testBlock process: execBlock isTrue: testBlock then: execBlock isTrue: testBlock process: execBlock Argument testBlock is a zero argument block that must evaluate to a boolean. "! ! !Case class methodsFor: 'examples'! enumerationTest: anInteger "Answer true if anInteger is included in collection else answer false. This can be combined with other caseIsAny:then: and case:then: statements to test aValue to be one of a combination of discrete values or passed any of several range tests." "Case enumerationTest: 6." "Case enumerationTest: 10." ^anInteger switch caseIsAny: #( 1 3 6 12 16) then: [ true ]; default: [ false ].! ! !Case class methodsFor: 'examples'! vendLargestAvailableCurrency: unVendedCurrenyValue "Similiar to Baumann's MoneyVendor>>vendLargestAvailableCurrency but modified to not require additional classes or new methods. " "Case vendLargestAvailableCurrency: 5" "Case vendLargestAvailableCurrency: 50" "Case vendLargestAvailableCurrency: 500" | hasHundred hasFifty hasTwenty hasTen | hasHundred := true. hasFifty := true. hasTwenty := true. hasTen := true. ^unVendedCurrenyValue switch case: [:value | value >= 100 and: [hasHundred]] then: ['vend hundred']; case: [:value | value >= 50 and: [hasFifty]] then: ['vend fifty']; case: [:value | value >= 20 and: [hasTwenty]] then: ['vend twenty']; case: [:value | value >= 10 and: [hasTen]] then: ['vend ten']; default: ['raise out of cash signal']! ! !Case class methodsFor: 'instance creation'! for: anObject ^self new criterion: anObject! !