| package |
package := Package name: 'NumberParser'.
package paxVersion: 1;
basicComment: 'NumberParser provide a class for parsing literal numbers
Main advantages are:
- use stream or string input
- able of error handling
- factor some code utility for several language/dialect syntaxes
Subclasses are used for specific syntax
- Fortran
- Smalltalk
- - Dolphin
- - Squeak
- - Visualworks'.
package classNames
add: #DolphinNumberParser;
add: #FORTRANNumberParser;
add: #FORTRANNumberParserTest;
add: #NumberParser;
add: #SmalltalkNumberParser;
add: #SqNumberParserTest;
add: #SqueakNumberParser;
add: #VWNumberParser;
yourself.
package methodNames
add: 'Float class' -> #negativeZero;
yourself.
package binaryGlobalNames: (Set new
yourself).
package globalAliases: (Set new
yourself).
package setPrerequisites: (IdentitySet new
add: 'E:\Documents and Settings\nicolas\My Documents\Dolphin Smalltalk X6\Object Arts\Dolphin\Base\Dolphin';
add: 'E:\Documents and Settings\nicolas\My Documents\Dolphin Smalltalk X6\Camp Smalltalk\SUnit\SUnit';
yourself).
package!
"Class Definitions"!
Object subclass: #NumberParser
instanceVariableNames: 'sourceStream base neg integerPart fractionPart exponent nDigits lastNonZero requestor failBlock'
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
NumberParser subclass: #FORTRANNumberParser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
NumberParser subclass: #SmalltalkNumberParser
instanceVariableNames: 'scale'
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
SmalltalkNumberParser subclass: #DolphinNumberParser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
SmalltalkNumberParser subclass: #SqueakNumberParser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
SmalltalkNumberParser subclass: #VWNumberParser
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
TestCase subclass: #FORTRANNumberParserTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
TestCase subclass: #SqNumberParserTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''!
"Global Aliases"!
"Loose Methods"!
!Float class methodsFor!
negativeZero
"Answer a negative zero which is the result of an underflow on a negative result"
^self fminDenormalized negated / self radix asFloat! !
!Float class categoriesFor: #negativeZero!constants!public! !
"End of package definition"!
"Source Globals"!
"Classes"!
NumberParser guid: (GUID fromString: '{8A729B92-F3DB-44D1-8C04-EF8E602BB4C6}')!
NumberParser comment: 'This is a class specialized in parsing character string or stream and building numbers.
It offers a framework with utility methods and exception handling.
Number syntax is not defined and should be subclassResponsibility.
'!
!NumberParser categoriesForClass!Unclassified! !
!NumberParser methodsFor!
allowPlusSign
"return a boolean indicating if plus sign is allowed or not"
^self subclassResponsibility!
allowPlusSignInExponent
"return a boolean indicating if plus sign is allowed or not in exponent"
^self allowPlusSign!
expected: errorString
requestor isNil
ifFalse:
["Code for interactive error to be inserted here...
example of code for Squeak compiler:
requestor
notify: errorString , ' ->'
at: sourceStream position
in: sourceStream"
].
self fail!
exponentLetters
"answer the list of possible exponents for Numbers."
^self subclassResponsibility!
fail
failBlock isNil ifFalse: [^failBlock value].
self error: 'Reading a number failed'!
failBlock: aBlockOrNil
failBlock := aBlockOrNil!
makeFloatFromMantissa: m exponent: k base: aRadix
"Convert infinite precision arithmetic into Floating point.
This algorithm rely on correct IEEE rounding mode
being implemented in Integer>>asFloat and Fraction>>asFloat"
^(k positive
ifTrue: [m * (aRadix raisedTo: k)]
ifFalse: [Fraction numerator: m denominator: (aRadix raisedTo: k negated)]) asFloat!
nextIntegerBase: aRadix
"Form an integer with following digits"
| isNeg value |
isNeg := self peekSignIsMinus.
value := self nextUnsignedIntegerBase: aRadix.
^isNeg ifTrue: [value negated] ifFalse: [value]!
nextIntegerBase: aRadix ifFail: aBlock
"Form an integer with following digits"
| isNeg value |
isNeg := self peekSignIsMinus.
value := self nextUnsignedIntegerBase: aRadix ifFail: [^aBlock value].
^isNeg ifTrue: [value negated] ifFalse: [value]!
nextMatchAll: aCollection
"this message was originally in Squeak Stream"
| savePosition |
savePosition := sourceStream position.
aCollection do:
[:each |
sourceStream next = each
ifFalse:
[sourceStream position: savePosition.
^false]].
^true!
nextNumber
"read next number from sourceStream contents"
^self subclassResponsibility!
nextUnsignedIntegerBase: aRadix
"Form an unsigned integer with incoming digits from sourceStream.
Count the number of digits and the lastNonZero digit and store int in
instVar "
^self nextUnsignedIntegerBase: aRadix ifFail: [self expected: 'a digit between 0 and 9']!
nextUnsignedIntegerBase: aRadix ifFail: errorBlock
"Form an unsigned integer with incoming digits from sourceStream.
Count the number of digits and the lastNonZero digit and store in instVar.
Answer the integer value read from sourceStream"
| value digit |
value := 0.
nDigits := 0.
lastNonZero := 0.
[sourceStream atEnd or:
[digit := sourceStream next digitValue.
(digit < 0 or: [digit >= aRadix]) and:
[sourceStream skip: -1.
true]]]
whileFalse:
[nDigits := nDigits + 1.
digit isZero ifFalse: [lastNonZero := nDigits].
value := value * aRadix + digit].
nDigits = 0 ifTrue: [errorBlock value].
^value!
on: aStringOrStream
sourceStream := aStringOrStream isString
ifTrue: [ReadStream on: aStringOrStream]
ifFalse: [aStringOrStream].
requestor := failBlock := nil!
peekSignIsMinus
"Peek an optional sign from sourceStream.
Answer true if it is minus sign"
| isMinus |
isMinus := sourceStream peekFor: $-.
isMinus ifFalse: [self allowPlusSign ifTrue: [sourceStream peekFor: $+]].
^isMinus!
readExponent
"read the exponent if any (stored in instVar).
Answer true if found, answer false if none.
If exponent letter is not followed by a digit,
this is not considered as an error.
Exponent are always read in base 10, though i do not see why..."
| eneg epos |
exponent := 0.
sourceStream atEnd ifTrue: [^false].
(self exponentLetters includes: sourceStream next)
ifFalse:
[sourceStream skip: -1.
^false].
eneg := sourceStream peekFor: $-.
epos := (eneg not and: [self allowPlusSignInExponent]) ifTrue: [sourceStream peekFor: $+] ifFalse: [false].
exponent := self nextUnsignedIntegerBase: 10
ifFail:
[sourceStream skip: (eneg | epos ifTrue: [-2] ifFalse: [-1]).
^false].
eneg ifTrue: [exponent := exponent negated].
^true!
requestor: anObjectOrNil
requestor := anObjectOrNil! !
!NumberParser categoriesFor: #allowPlusSign!accessing!public! !
!NumberParser categoriesFor: #allowPlusSignInExponent!accessing!public! !
!NumberParser categoriesFor: #expected:!error handling!public! !
!NumberParser categoriesFor: #exponentLetters!accessing!public! !
!NumberParser categoriesFor: #fail!error handling!public! !
!NumberParser categoriesFor: #failBlock:!accessing!public! !
!NumberParser categoriesFor: #makeFloatFromMantissa:exponent:base:!private! !
!NumberParser categoriesFor: #nextIntegerBase:!parsing!public! !
!NumberParser categoriesFor: #nextIntegerBase:ifFail:!parsing!public! !
!NumberParser categoriesFor: #nextMatchAll:!private! !
!NumberParser categoriesFor: #nextNumber!parsing!public! !
!NumberParser categoriesFor: #nextUnsignedIntegerBase:!parsing!public! !
!NumberParser categoriesFor: #nextUnsignedIntegerBase:ifFail:!parsing!public! !
!NumberParser categoriesFor: #on:!initialize/release!public! !
!NumberParser categoriesFor: #peekSignIsMinus!parsing!private! !
!NumberParser categoriesFor: #readExponent!parsing!private! !
!NumberParser categoriesFor: #requestor:!accessing!public! !
!NumberParser class methodsFor!
on: aStringOrStream
^self new on: aStringOrStream!
parse: aStringOrStream
^(self new)
on: aStringOrStream;
nextNumber!
parse: aStringOrStream onError: failBlock
^(self new)
on: aStringOrStream;
failBlock: failBlock;
nextNumber! !
!NumberParser class categoriesFor: #on:!instance creation!public! !
!NumberParser class categoriesFor: #parse:!instance creation!public! !
!NumberParser class categoriesFor: #parse:onError:!instance creation!public! !
FORTRANNumberParser guid: (GUID fromString: '{77D4E71B-D173-4478-91A7-518CB9B8AB89}')!
FORTRANNumberParser comment: 'This class is able to parse ASCII representation of numbers generated by FORTRAN programs.
Possible syntax:
digit = ''0'' | ''1'' | ''2'' | ''3'' | ''4'' | ''5'' | ''6'' | ''7'' | ''8'' | ''9'' ;
sign = ''+'' | ''-'';
integer = [sign] digit{digit} ;
float = [sign] [digit{digit}] [''.''] digit{digit} [(''E'' | ''D'' ) [sign] digit{digit} ] ;
number = integer | float ;
Examples:
124
+124
-124
1.0
1.
.23
1E+5
1.0E-3
.1E-22
3.01D+55
Not accepted: exponent letter is sometimes omitted for double precision with 3 digits exponent...
1.001-123
Not accepted: complex numbers into parentheses
(1.0 , 3.11)'!
!FORTRANNumberParser categoriesForClass!Unclassified! !
!FORTRANNumberParser methodsFor!
allowPlusSign
^true!
exponentLetters
"answer the list of possible exponents for Numbers.
Note: this parser will not honour precision attached to the exponent.
different exponent do not lead to different precisions.
only IEEE 754 double precision floating point numbers will be created"
^'ED'!
nextFloat
^self nextNumber asFloat!
nextNumber
"main method for reading a number with FORTRAN syntax.
This one can read Real and Integer (not complex)"
| numberOfTrailingZeroInIntegerPart numberOfNonZeroFractionDigits mantissa value numberOfTrailingZeroInFractionPart noInt |
base := 10.
(self nextMatchAll: 'NaN') ifTrue: [^Float nan].
neg := self peekSignIsMinus.
(self nextMatchAll: 'Infinity')
ifTrue: [^neg ifTrue: [Float infinity negated] ifFalse: [Float infinity]].
(noInt := sourceStream peekFor: $.)
ifTrue:
[integerPart := 0.
numberOfTrailingZeroInIntegerPart := 0]
ifFalse:
[integerPart := self nextUnsignedIntegerBase: base.
numberOfTrailingZeroInIntegerPart := nDigits - lastNonZero].
(noInt or: [sourceStream peekFor: $.])
ifTrue:
[fractionPart := self nextUnsignedIntegerBase: base ifFail: [nil].
fractionPart isNil
ifTrue:
[noInt
ifTrue:
["no interger part, no fraction part..."
self expected: 'a digit 0 to 9'.
^nil].
fractionPart := 0]
ifFalse:
[numberOfNonZeroFractionDigits := lastNonZero.
numberOfTrailingZeroInFractionPart := nDigits - lastNonZero].
self readExponent]
ifFalse:
[self readExponent ifFalse: [^neg ifTrue: [integerPart negated] ifFalse: [integerPart]].
fractionPart := 0].
fractionPart isZero
ifTrue:
[mantissa := integerPart // (base raisedTo: numberOfTrailingZeroInIntegerPart).
exponent := exponent + numberOfTrailingZeroInIntegerPart]
ifFalse:
[mantissa := integerPart * (base raisedTo: numberOfNonZeroFractionDigits)
+ (fractionPart // (base raisedTo: numberOfTrailingZeroInFractionPart)).
exponent := exponent - numberOfNonZeroFractionDigits].
value := self
makeFloatFromMantissa: mantissa
exponent: exponent
base: base.
^neg ifTrue: [value isZero ifTrue: [Float negativeZero] ifFalse: [value negated]] ifFalse: [value]! !
!FORTRANNumberParser categoriesFor: #allowPlusSign!accessing!public! !
!FORTRANNumberParser categoriesFor: #exponentLetters!accessing!public! !
!FORTRANNumberParser categoriesFor: #nextFloat!parsing!public! !
!FORTRANNumberParser categoriesFor: #nextNumber!parsing!public! !
SmalltalkNumberParser guid: (GUID fromString: '{9D05B2BF-FB07-4FB9-ABD5-90A55668723A}')!
SmalltalkNumberParser comment: 'This is a class specialized in parsing and building numbers.
Number syntax should follow Smalltalk syntax.
It does handle number literals
- float
- integer
- scaled decimal'!
!SmalltalkNumberParser categoriesForClass!Unclassified! !
!SmalltalkNumberParser methodsFor!
allowPlusSign
"default behaviour is not to allow + sign in literal number syntax"
^false!
exponentLetters
"answer the list of possible exponents for Numbers.
Note: this parser will not honour precision attached to the exponent.
different exponent do not lead to different precisions.
only IEEE 754 double precision floating point numbers will be created"
^'edq'!
makeIntegerOrScaledInteger
"at this point, there is no digit, nor fractionPart.
maybe it can be a scaled decimal with fraction omitted..."
neg ifTrue: [integerPart := integerPart negated].
self readExponent
ifTrue: [integerPart := integerPart * (base raisedTo: exponent)]
ifFalse:
[(self readScaleWithDefaultNumberOfDigits: 0)
ifTrue:
[nil.
^ScaledDecimal newFromNumber: integerPart scale: scale]].
^integerPart!
nextNumber
"main method for reading a number.
This one can read Float Integer and ScaledDecimal"
| numberOfTrailingZeroInIntegerPart numberOfNonZeroFractionDigits mantissa decimalMultiplier decimalFraction value numberOfTrailingZeroInFractionPart numberOfFractionDigits |
base := 10.
neg := sourceStream peekFor: $-.
integerPart := self nextUnsignedIntegerBase: base.
numberOfTrailingZeroInIntegerPart := nDigits - lastNonZero.
(sourceStream peekFor: $r)
ifTrue:
["r"
(base := integerPart) < 2 ifTrue: [^self expected: 'an integer greater than 1 as valid radix'].
(sourceStream peekFor: $-) ifTrue: [neg := neg not].
integerPart := self nextUnsignedIntegerBase: base.
^neg ifTrue: [integerPart negated ] ifFalse: [integerPart]].
^(sourceStream peekFor: $.)
ifTrue:
[fractionPart := self nextUnsignedIntegerBase: base
ifFail:
[sourceStream skip: -1.
^neg ifTrue: [integerPart negated] ifFalse: [integerPart]].
numberOfNonZeroFractionDigits := lastNonZero.
numberOfTrailingZeroInFractionPart := nDigits - lastNonZero.
numberOfFractionDigits := nDigits.
self readExponent
ifFalse:
[(self readScaleWithDefaultNumberOfDigits: numberOfFractionDigits)
ifTrue:
[decimalMultiplier := base raisedTo: numberOfNonZeroFractionDigits.
decimalFraction := (integerPart * decimalMultiplier + fractionPart) / decimalMultiplier.
neg ifTrue: [decimalFraction := decimalFraction negated].
^ScaledDecimal newFromNumber: decimalFraction scale: scale]].
fractionPart isZero
ifTrue:
[mantissa := integerPart // (base raisedTo: numberOfTrailingZeroInIntegerPart).
exponent := exponent + numberOfTrailingZeroInIntegerPart]
ifFalse:
[mantissa := integerPart * (base raisedTo: numberOfNonZeroFractionDigits)
+ (fractionPart // (base raisedTo: numberOfTrailingZeroInFractionPart)).
exponent := exponent - numberOfNonZeroFractionDigits].
value := self
makeFloatFromMantissa: mantissa
exponent: exponent
base: base.
^neg ifTrue: [value negated] ifFalse: [value]]
ifFalse: [self makeIntegerOrScaledInteger]!
readScaleWithDefaultNumberOfDigits: anInteger
"read the scale if any (stored in instVar).
Answer true if found, answer false if none.
If scale letter is not followed by a digit,
then answer the default number of digits (anInteger)"
scale := 0.
sourceStream atEnd ifTrue: [^false].
('s' includes: sourceStream next)
ifFalse:
[sourceStream skip: -1.
^false].
scale := self nextUnsignedIntegerBase: 10 ifFail: [anInteger].
^true! !
!SmalltalkNumberParser categoriesFor: #allowPlusSign!accessing!public! !
!SmalltalkNumberParser categoriesFor: #exponentLetters!accessing!public! !
!SmalltalkNumberParser categoriesFor: #makeIntegerOrScaledInteger!private! !
!SmalltalkNumberParser categoriesFor: #nextNumber!parsing!public! !
!SmalltalkNumberParser categoriesFor: #readScaleWithDefaultNumberOfDigits:!private! !
DolphinNumberParser guid: (GUID fromString: '{AC513FE1-F5CC-44BC-9977-69F3A405AB7B}')!
DolphinNumberParser comment: 'Parse and build numbers according to Dolphin syntax.
Dolphin specific:
- allow + sign after exponent'!
!DolphinNumberParser categoriesForClass!Unclassified! !
!DolphinNumberParser methodsFor!
allowPlusSignInExponent
^true! !
!DolphinNumberParser categoriesFor: #allowPlusSignInExponent!accessing!public! !
SqueakNumberParser guid: (GUID fromString: '{859A0728-0881-4753-9300-2681FDF5B6EA}')!
SqueakNumberParser comment: 'Parse and build numbers according to squeak syntax.
Squeak specific:
- allow 2r-10 and -2r10 and even -2r-10
- allow floating point with radix 2r10.011
- do not allow single s without following digits as ScaledDecimal
Handle special case of Float (NaN Infinity and -0.0 as negative zero)'!
!SqueakNumberParser categoriesForClass!Unclassified! !
!SqueakNumberParser methodsFor!
nextNumber
"main method for reading a number.
This one can read Float Integer and ScaledDecimal"
| numberOfTrailingZeroInIntegerPart numberOfNonZeroFractionDigits mantissa decimalMultiplier decimalFraction value numberOfTrailingZeroInFractionPart |
(self nextMatchAll: 'NaN') ifTrue: [^Float nan].
neg := sourceStream peekFor: $-.
(self nextMatchAll: 'Infinity')
ifTrue: [^neg ifTrue: [Float infinity negated] ifFalse: [Float infinity]].
base := 10.
integerPart := self nextUnsignedIntegerBase: base.
numberOfTrailingZeroInIntegerPart := nDigits - lastNonZero.
(sourceStream peekFor: $r)
ifTrue:
["r"
(base := integerPart) < 2 ifTrue: [^self expected: 'an integer greater than 1 as valid radix'].
(sourceStream peekFor: $-) ifTrue: [neg := neg not].
integerPart := self nextUnsignedIntegerBase: base.
numberOfTrailingZeroInIntegerPart := nDigits - lastNonZero].
^(sourceStream peekFor: $.)
ifTrue:
[fractionPart := self nextUnsignedIntegerBase: base
ifFail:
[sourceStream skip: -1.
^neg ifTrue: [integerPart negated] ifFalse: [integerPart]].
numberOfNonZeroFractionDigits := lastNonZero.
numberOfTrailingZeroInFractionPart := nDigits - lastNonZero.
self readExponent
ifFalse:
[self readScale
ifTrue:
[decimalMultiplier := base raisedTo: numberOfNonZeroFractionDigits.
decimalFraction := (integerPart * decimalMultiplier + fractionPart) / decimalMultiplier.
neg ifTrue: [decimalFraction := decimalFraction negated].
^ScaledDecimal newFromNumber: decimalFraction scale: scale]].
fractionPart isZero
ifTrue:
[mantissa := integerPart // (base raisedTo: numberOfTrailingZeroInIntegerPart).
exponent := exponent + numberOfTrailingZeroInIntegerPart]
ifFalse:
[mantissa := integerPart * (base raisedTo: numberOfNonZeroFractionDigits)
+ (fractionPart // (base raisedTo: numberOfTrailingZeroInFractionPart)).
exponent := exponent - numberOfNonZeroFractionDigits].
value := self
makeFloatFromMantissa: mantissa
exponent: exponent
base: base.
^neg ifTrue: [value isZero ifTrue: [Float negativeZero] ifFalse: [value negated]] ifFalse: [value]]
ifFalse: [self makeIntegerOrScaledInteger]!
readScale
"read the scale if any (stored in instVar).
Answer true if found, answer false if none.
If scale letter is not followed by a digit,
this is not considered as an error.
Scales are always read in base 10, though i do not see why..."
scale := 0.
sourceStream atEnd ifTrue: [^false].
('s' includes: sourceStream next)
ifFalse:
[sourceStream skip: -1.
^false].
scale := self nextUnsignedIntegerBase: 10
ifFail:
[sourceStream skip: -1.
^false].
^true!
readScaleWithDefaultNumberOfDigits: anInteger
"read the scale if any (stored in instVar).
Answer true if found, answer false if none.
SQUEAK SPECIFIC:If scale letter is not followed by a digit
Do not use default number of digits, but rather answer false"
^self readScale! !
!SqueakNumberParser categoriesFor: #nextNumber!parsing!public! !
!SqueakNumberParser categoriesFor: #readScale!private! !
!SqueakNumberParser categoriesFor: #readScaleWithDefaultNumberOfDigits:!private! !
VWNumberParser guid: (GUID fromString: '{2614DEFE-DA3A-4307-8D95-9DC9A98A0464}')!
VWNumberParser comment: 'Parse and build numbers according to VisualWorks syntax.
VW specific:
- allow exponent character without following digits'!
!VWNumberParser categoriesForClass!Unclassified! !
!VWNumberParser methodsFor!
makeIntegerOrScaledInteger
"Same as super except that exponent letter will lead to "
neg ifTrue: [integerPart := integerPart negated].
self readExponent
ifTrue:
[^self
makeFloatFromMantissa: integerPart
exponent: exponent
base: base].
(self readScaleWithDefaultNumberOfDigits: 0)
ifTrue:
[nil.
^ScaledDecimal newFromNumber: integerPart scale: scale].
^integerPart!
readExponent
"read the exponent if any (stored in instVar).
Answer true if found, answer false if none.
If exponent letter is not followed by a digit,
it is assumed to be zero"
| eneg epos |
exponent := 0.
sourceStream atEnd ifTrue: [^false].
(self exponentLetters includes: sourceStream next)
ifFalse:
[sourceStream skip: -1.
^false].
eneg := sourceStream peekFor: $-.
epos := (eneg not and: [self allowPlusSignInExponent]) ifTrue: [sourceStream peekFor: $+] ifFalse: [false].
exponent := self nextUnsignedIntegerBase: 10
ifFail:
[eneg | epos ifTrue: [sourceStream skip: -1].
0].
eneg ifTrue: [exponent := exponent negated].
^true! !
!VWNumberParser categoriesFor: #makeIntegerOrScaledInteger!public! !
!VWNumberParser categoriesFor: #readExponent!private! !
FORTRANNumberParserTest guid: (GUID fromString: '{890CFC1F-59BF-4468-9B50-EDA60A66F94D}')!
FORTRANNumberParserTest comment: ''!
!FORTRANNumberParserTest categoriesForClass!Unclassified! !
!FORTRANNumberParserTest methodsFor!
testFloat
| rs i |
rs := ReadStream on: '712.'.
i := FORTRANNumberParser parse: rs.
self assert: i = 712.0.
rs := ReadStream on: '-23.5'.
i := FORTRANNumberParser parse: rs.
self assert: i = -23.5.
rs := ReadStream on: '+.28E2'.
i := FORTRANNumberParser parse: rs.
self assert: i = 28.0.
rs := ReadStream on: '+.28D+2'.
i := FORTRANNumberParser parse: rs.
self assert: i = 28.0.
rs := ReadStream on: '125.E-3'.
i := FORTRANNumberParser parse: rs.
self assert: i = 0.125.
!
testInteger
| rs i |
rs := ReadStream on: '712'.
i := FORTRANNumberParser parse: rs.
self assert: i = 712.
rs := ReadStream on: '-235'.
i := FORTRANNumberParser parse: rs.
self assert: i = -235.
rs := ReadStream on: '+28'.
i := FORTRANNumberParser parse: rs.
self assert: i = 28.
! !
!FORTRANNumberParserTest categoriesFor: #testFloat!public! !
!FORTRANNumberParserTest categoriesFor: #testInteger!public! !
SqNumberParserTest guid: (GUID fromString: '{BD41DE41-0235-48A7-896C-DF79CA415EA5}')!
SqNumberParserTest comment: ''!
!SqNumberParserTest categoriesForClass!Unclassified! !
!SqNumberParserTest methodsFor!
testFloatFromStreamAsNumber
"This covers parsing in Number>>readFrom:"
| rs aFloat |
rs := '10r-12.3456' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: -12.3456 = aFloat.
self assert: rs atEnd.
rs := '10r-12.3456e2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: -1234.56 = aFloat.
self assert: rs atEnd.
rs := '10r-12.3456e2e2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: -1234.56 = aFloat.
self assert: rs upToEnd = 'e2'.
rs := '10r-12.3456d2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: -1234.56 = aFloat.
self assert: rs atEnd.
rs := '10r-12.3456q2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: -1234.56 = aFloat.
self assert: rs atEnd.
rs := '-12.3456q2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: -1234.56 = aFloat.
self assert: rs atEnd.
rs := '12.3456q2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 1234.56 = aFloat.
self assert: rs atEnd.
rs := '12.3456z2' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 12.3456 = aFloat.
self assert: rs upToEnd = 'z2'.
!
testFloatFromStreamWithExponent
"This covers parsing in Number>>readFrom:"
| rs aFloat |
rs := '1.0e-14' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 1.0e-14 = aFloat.
self assert: rs atEnd.
rs := '1.0e-14 1' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 1.0e-14 = aFloat.
self assert: rs upToEnd = ' 1'.
rs := '1.0e-14eee' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 1.0e-14 = aFloat.
self assert: rs upToEnd = 'eee'.
rs := '1.0e14e10' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 1.0e14 = aFloat.
self assert: rs upToEnd = 'e10'.
rs := '1.0e+14e' readStream. "Plus sign is not parseable"
aFloat := SqueakNumberParser parse: rs.
self assert: 1.0 = aFloat.
self assert: rs upToEnd = 'e+14e'.
rs := '1.0e' readStream.
aFloat := SqueakNumberParser parse: rs.
self assert: 1.0 = aFloat.
self assert: rs upToEnd = 'e'.!
testFloatPrintString
"self debug: #testFloatPrintString"
| bytes f r |
bytes := ByteArray new: 8.
r := Random new seed: 1234567.
100
timesRepeat: [bytes dwordAtOffset: 4 put: (r next * 16r100000000) truncated.
bytes dwordAtOffset: 0 put: (r next * 16r100000000) truncated.
f := bytes floatAtOffset: 0.
#(10)
do: [:base | | str |
str := (String new: 64) writeStream.
f negative ifTrue: [str nextPut: $-].
str print: base; nextPut: $r.
f abs printOn: str significantFigures: 20.
self assert: (SqueakNumberParser parse: str contents) = f]].
"test big num near infinity"
10
timesRepeat: [bytes dwordAtOffset: 4 put: 16r7FE00000 + ((r next * 16r100000) truncated).
bytes dwordAtOffset: 0 put: (r next * 16r100000000) truncated.
f := bytes floatAtOffset: 0.
#(10)
do: [:base | | str |
str := (String new: 64) writeStream.
f negative ifTrue: [str nextPut: $-].
str print: base; nextPut: $r.
f abs printOn: str significantFigures: 20.
self assert: (SqueakNumberParser parse: str contents) = f]].
"test infinitesimal (gradual underflow)"
10
timesRepeat: [bytes dwordAtOffset: 4 put: 0 + ((r next * 16r100000) truncated).
bytes dwordAtOffset: 0 put: (r nextInt: 16r100000000) truncated.
f := bytes floatAtOffset: 0.
#(2 8 10 16)
do: [:base | | str |
str := (String new: 64) writeStream.
f negative ifTrue: [str nextPut: $-].
str print: base; nextPut: $r.
f abs printOn: str significantFigures: 20.
self assert: (SqueakNumberParser parse: str contents) = f]].!
testFloatReadError
"This covers parsing in Number>>readFrom:"
| rs num |
rs := '1e' readStream.
num := SqueakNumberParser parse: rs.
self assert: 1 = num.
self assert: rs upToEnd = 'e'.
rs := '1s' readStream.
num := SqueakNumberParser parse: rs.
self assert: 1 = num.
self assert: rs upToEnd = 's'.
rs := '1.' readStream.
num := SqueakNumberParser parse: rs.
self assert: 1 = num.
self assert: num isInteger.
self assert: rs upToEnd = '.'.
rs := '' readStream.
self should: [SqueakNumberParser parse: rs] raise: Error.
rs := 'foo' readStream.
self should: [SqueakNumberParser parse: rs] raise: Error.
rs := 'radix' readStream.
self should: [SqueakNumberParser parse: rs] raise: Error.
rs := '.e0' readStream.
self should: [SqueakNumberParser parse: rs] raise: Error.
rs := '-.e0' readStream.
self should: [SqueakNumberParser parse: rs] raise: Error.
rs := '--1' readStream.
self should: [SqueakNumberParser parse: rs] raise: Error.!
testFloatReadWithRadix
"This covers parsing in Number>>readFrom:
Note: In most Smalltalk dialects, the radix notation is not used for numbers
with exponents. In Squeak, a string with radix and exponent can be parsed,
and the exponent is always treated as base 10 (not the base indicated in the
radix prefix). I am not sure if this is a feature, a bug, or both, but the
Squeak behavior is documented in this test. -dtl"
| aNumber rs |
aNumber := (2r10101 / 2r10000) asFloat timesTwoPower: 9.
self assert: 672.0 = aNumber.
self assert: (SqueakNumberParser parse: '2r1.0101e9') = (1.3125 * (2 raisedTo: 9)).
rs := ReadStream on: '2r1.0101e9e9'.
self assert: (SqueakNumberParser parse: rs) = 672.0.
self assert: rs upToEnd = 'e9'
!
testIntegerReadFrom
"Ensure remaining characters in a stream are not lost when parsing an integer."
| rs i s |
rs := ReadStream on: '123s could be confused with a ScaledDecimal'.
i := SqueakNumberParser parse: rs.
self assert: i == 123.
s := rs upToEnd.
self assert: 's could be confused with a ScaledDecimal' = s.
rs := ReadStream on: '123.s could be confused with a ScaledDecimal'.
i := SqueakNumberParser parse: rs.
self assert: i == 123.
s := rs upToEnd.
self assert: '.s could be confused with a ScaledDecimal' = s
!
testIntegerReadWithRadix
"This covers parsing in Number>>readFrom:
Note: In most Smalltalk dialects, the radix notation is not used for numbers
with exponents. In Squeak, a string with radix and exponent can be parsed,
and the exponent is always treated as base 10 (not the base indicated in the
radix prefix). I am not sure if this is a feature, a bug, or both, but the
Squeak behavior is documented in this test. -dtl"
| aNumber rs |
aNumber := 2 raisedTo: 26.
self assert: (SqueakNumberParser parse: '2r1e26') = aNumber.
rs := '2r1e26eee' readStream.
self assert: (SqueakNumberParser parse: rs) = aNumber.
self assert: rs upToEnd = 'eee'
! !
!SqNumberParserTest categoriesFor: #testFloatFromStreamAsNumber!public! !
!SqNumberParserTest categoriesFor: #testFloatFromStreamWithExponent!public! !
!SqNumberParserTest categoriesFor: #testFloatPrintString!public! !
!SqNumberParserTest categoriesFor: #testFloatReadError!public! !
!SqNumberParserTest categoriesFor: #testFloatReadWithRadix!public! !
!SqNumberParserTest categoriesFor: #testIntegerReadFrom!public! !
!SqNumberParserTest categoriesFor: #testIntegerReadWithRadix!public! !
"Binary Globals"!