PrintfScanf.st
changeset 1463 abe8e819ea92
child 1464 10af50b07968
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/PrintfScanf.st	Tue Jul 27 10:28:19 2004 +0200
@@ -0,0 +1,517 @@
+"{ Package: 'stx:libbasic2' }"
+
+Object subclass:#PrintfScanf
+	instanceVariableNames:''
+	classVariableNames:''
+	poolDictionaries:''
+	category:'System-Support'
+!
+
+!PrintfScanf class methodsFor:'documentation'!
+
+examples
+"
+    self new printf:'%#x %#X %03o%*.*s' arguments: #(16rABCD 16rEF 5 9 5 ''ghijklmn'') 
+
+    self new printf:'%- 10.4s%.2e' arguments: (Array with: 'abcdefghijkl' with: Float pi)  
+
+    self new printf:'%8.3f' arguments: (Array with: 200 sqrt negated)
+
+    self new printf:'%c' arguments: #(16r41)
+
+    self new sscanf:'%f%2s%s%s%s' string: '237.0 this is a test' 
+
+    self new sscanf:'%d%f%s' string: '25 54.32e-01 monday'
+
+    self new sscanf:'%f%*f %8[A-F0-9]%c%d 0x%x%f' string: '12.45 1048.73 AE40Z527 0x75BCD15 34'
+
+
+
+
+    '%#x %#X %03o%*.*s' printf: #(16rABCD 16rEF 5 9 5 ''ghijklmn'') 
+
+
+    '%- 10.4s%.2e' printf: (Array with: 'abcdefghijkl' with: Float pi)  
+
+    '%8.3f' printf: (Array with: 200 sqrt negated)
+
+    '%c' printf: #(16r41)
+
+    '%f%2s%s%s%s' sscanf: '237.0 this is a test' 
+
+    '%d%f%s' sscanf: '25 54.32e-01 monday'
+
+    '%f%*f %8[A-F0-9]%c%d 0x%x%f' sscanf: '12.45 1048.73 AE40Z527 0x75BCD15 34'
+"
+! !
+
+!PrintfScanf methodsFor:'printing'!
+
+printArgFrom:inStream to:outStream arguments:argStream
+    "Interpret the required number of arguments from <argStream>
+     according to the formatting information in <inStream>.  Place
+     the interpretation on <outStream>.  The interpretation is C
+     printf(3) style, as described in the UTek manual page for
+     printf(3).  <inStream> is assumed to be positioned just past
+     $%, and a complete control string is assumed available.     
+
+     Return when the conversion control string is consumed.  
+     Leave <inStream> pointing past the last character in the conversion control string.
+
+     This code assumes that <inStream> is formatted according to
+     specification, and error checking is minimal.  Unexpected
+     results will be obtained by illegal control strings, or when
+     argument types do not match conversion codes, but it probably
+     won't dump core, like C does in such cases!!!!"    
+
+    | ljust plus pound width precision pad char arg argString sci |
+
+    ljust := plus := pound := false.
+    width := 0.
+    precision := SmallInteger maxVal.
+    pad := $ .
+    char := inStream peek.
+
+    char == $% ifTrue:[ 
+        ^ outStream nextPut: inStream next
+    ].
+
+    char == $- ifTrue:[
+        ljust := true.  
+        inStream next.  
+        char := inStream peek
+    ].
+
+    char == $  ifTrue:[
+        outStream space.  
+        inStream next.  
+        char := inStream peek
+    ].
+
+    char == $+ ifTrue:[
+        plus := true.  
+        inStream next. 
+        char := inStream peek
+    ].
+
+    char == $# ifTrue:[
+        pound := true.  
+        inStream next.  
+        char := inStream peek
+    ].
+
+    char == $* ifTrue:[
+        width := argStream next.  
+        inStream next.  
+        char := inStream peek
+    ].
+
+    char isDigit ifTrue:[
+        char == $0 ifTrue: [pad _ $0].
+        width := Integer readFrom: inStream.  
+        char := inStream peek
+    ].
+
+    char == $. ifTrue:[
+        inStream next.  char _ inStream peek.
+        char == $*
+                    ifTrue: [precision _ argStream next.  inStream next.  char _ inStream peek]
+                    ifFalse: [precision _ Integer readFrom: inStream.  char _ inStream peek]
+    ].
+
+    char == $l ifTrue:[
+        "Ignore long specifier."
+        inStream next.  char _ inStream peek
+    ].
+
+    ('feg' includes: char) ifTrue:[
+            arg _ argStream next asFloat.
+            precision _ precision min: 6.
+            argString _ WriteStream on: String new.
+            char == $g ifTrue:
+                    [self absPrintFloat:arg on: argString digits: precision + 1].
+            char == $f ifTrue:
+                    [self absDecimalPrintFloat:arg on: argString digits: precision + arg abs log + 1].
+            char == $e ifTrue:
+                    [self absScientificPrintFloat:arg on: argString digits: precision + 1].
+            argString _ argString contents.
+            arg < 0
+                    ifTrue: [argString _ '-', argString]
+                    ifFalse: [plus ifTrue: [argString _ '+', argString]].
+            (precision = 0 and: [pound not]) ifTrue:
+                    [(argString includes: $e)
+                            ifTrue: ["self halt"]
+                            ifFalse:
+                                    [argString _ arg truncated printString]].
+            pound ifTrue:
+                    [(argString includes: $e)
+                            ifTrue: ["self halt"]
+                            ifFalse:
+                                    [precision - (argString size - (argString indexOf: $.)) timesRepeat:
+                                            [argString _ argString, '0']]].
+            ljust ifTrue: [outStream nextPutAll: argString].
+            width - argString size timesRepeat: [outStream space].
+            ljust ifFalse: [outStream nextPutAll: argString].
+            ^inStream next
+    ].
+
+    char == $c ifTrue:[
+        arg _ String with: argStream next asCharacter
+    ].
+        
+    char == $s ifTrue:[
+        "Assume the arg is a String or Symbol."
+        arg _ argStream next asString
+    ].
+
+    char == $d ifTrue:[
+        arg _ argStream next asInteger printString.
+        plus ifTrue: [arg _ '+', arg]
+    ].
+
+    char == $u ifTrue:[
+        arg _ argStream next asInteger abs printString
+    ].
+
+    char == $o ifTrue:[
+        arg _ argStream next asInteger abs printStringRadix: 8.
+            pound ifTrue: [arg _ '0', arg]
+    ].
+
+    ('xX' includes: char) ifTrue:[
+        arg _ argStream next asInteger abs printStringRadix: 16.
+        pound ifTrue: [arg _ '0x', arg]
+    ].
+
+    char == $x ifTrue:[
+        1 to: arg size do: [:i |
+                    ('ABCDEF' includes: (arg at: i)) ifTrue:
+                            [arg at: i put: ((arg at: i) asciiValue + 16r20) asCharacter]]
+    ].
+
+    precision _ precision min: arg size.
+    ljust ifTrue: [outStream nextPutAll: (arg copyFrom: 1 to: precision)].
+    width - precision timesRepeat: [outStream nextPut: pad].
+    ljust ifFalse: [outStream nextPutAll: (arg copyFrom: 1 to: precision)].
+    ^ inStream next
+!
+
+printf:aString arguments:args 
+    "Format and print the receiver with <args> formatted in C style, 
+     as described in the UTek manual page for printf(3)."
+    
+    |aStream|
+
+    aStream := WriteStream on:String new.
+    self 
+        printf:aString
+        on:aStream
+        arguments:args.
+    ^ aStream contents
+
+    "
+     self new printf:'%d %x' arguments:#(1234 45054) 
+    "
+!
+
+printf:aFormatString on:outStream arguments: args
+    "Format and print aFormatString on <outStream> with <args>
+     formatted in C style, as described in the UTek manual page for
+     printf(3).  This method is designed for producing output
+     suitable for a machine."     
+
+    | argStream inStream char |
+
+    argStream := ReadStream on: args.
+    inStream := ReadStream on: aFormatString.
+    [inStream atEnd] whileFalse:[
+        (char := inStream next) == $% ifFalse: [
+            outStream nextPut: char
+        ] ifTrue: [
+            self printArgFrom:inStream to:outStream arguments:argStream
+        ]
+    ]
+! !
+
+!PrintfScanf methodsFor:'queries'!
+
+absDecimalPrintFloat:aFloat on:aStream digits:digits 
+    "Place a string representation of the receiver on <aStream> using <digits> significant digits, using decimal notation."
+    
+    |exp x fuzz i|
+
+    "x is myself normalized to (1.0, 10.0), exp is my exponent"
+    exp := aFloat abs < 1.0 ifTrue:[
+                (10.0 / aFloat abs) log floor negated
+            ] ifFalse:[
+                aFloat abs log floor
+            ].
+    x := aFloat abs / (10.0 raisedTo:exp).
+    fuzz := 10.0 raisedTo:1 - digits.
+    x := 0.5 * fuzz + x.
+    x >= 10.0 ifTrue:[ 
+        "check if rounding has unnormalized x" 
+        x := x / 10.0.
+        exp := exp + 1
+    ].
+    exp < 0 ifTrue:[
+        1 to:1 - exp do:[:j | 
+            aStream nextPut:('0.000000000000' at:j)
+        ]
+    ].
+    [ x >= fuzz ] whileTrue:[ 
+        "use fuzz to track significance" 
+        i := x truncated.
+        aStream nextPut:(48 + i) asCharacter.
+        x := (x - i) * 10.0.
+        fuzz := fuzz * 10.0.
+        exp := exp - 1.
+        exp = -1 ifTrue:[
+            aStream nextPut:$.
+        ]
+    ].
+    [ exp >= -1 ] whileTrue:[
+        aStream nextPut:$0.
+        exp := exp - 1.
+        exp = -1 ifTrue:[
+            aStream nextPut:$.
+        ]
+    ]
+!
+
+absPrintFloat:aFloat on:aStream digits:digits 
+    "Place a string representation of the receiver on <aStream> using <digits> significant digits."
+    
+    (aFloat < 1.0e6 and:[ aFloat > 1.0e-4 ]) ifTrue:[
+        self 
+            absDecimalPrintFloat:aFloat
+            on:aStream
+            digits:digits
+    ] ifFalse:[
+        aFloat 
+            absScientificPrintFloat:aFloat
+            on:aStream
+            digits:digits
+    ]
+!
+
+absScientificPrintFloat:aFloat on:aStream digits:digits 
+    "Place a string representation of the receiver on <aStream> using <digits> significant digits, using scientific notation."
+    
+    |exp fuzz x q i|
+
+    "x is myself normalized to [1.0, 10.0), exp is my exponent"
+    exp := aFloat abs < 1.0 ifTrue:[
+                (10.0 / aFloat abs) log floor negated
+            ] ifFalse:[
+                aFloat abs log floor
+            ].
+    x := aFloat abs / (10.0 raisedTo:exp).
+    fuzz := 10.0 raisedTo:1 - digits.
+    x := 0.5 * fuzz + x.
+    x >= 10.0 ifTrue:[ "check if rounding has unnormalized x" 
+        x := x / 10.0.
+        exp := exp + 1
+    ].
+    q := exp.
+    exp := 0.
+    [ x >= fuzz ] whileTrue:[
+        "use fuzz to track significance" 
+        i := x truncated.
+        aStream nextPut:(48 + i) asCharacter.
+        x := (x - i) * 10.0.
+        fuzz := fuzz * 10.0.
+        exp := exp - 1.
+        exp = -1 ifTrue:[
+            aStream nextPut:$.
+        ]
+    ].
+    [ exp >= -1 ] whileTrue:[
+        aStream nextPut:$0.
+        exp := exp - 1.
+        exp = -1 ifTrue:[
+            aStream nextPut:$.
+        ]
+    ].
+    aStream nextPut:$e.
+    q printOn:aStream
+!
+
+formatArgCountFor:aFormatString
+    "Return the number of arguments required/produced,
+     if the argument is interpreted as a printf/scanf format control string."
+
+    |nonConsecutive count|
+
+    nonConsecutive := true.
+    count := 0.
+    aFormatString do:[:c |
+        c == $% ifTrue:[
+            nonConsecutive ifTrue:[
+                count := count + 1. 
+                nonConsecutive := false
+            ] ifFalse:[
+                count := count - 1. 
+                nonConsecutive := true
+            ]
+        ] ifFalse:[
+            nonConsecutive := true
+        ]
+    ].
+    ^ count
+! !
+
+!PrintfScanf methodsFor:'scanning'!
+
+scanArgFrom:dataStream to:collection format:format 
+    "Add to <collection> an object who's representation is found
+     in <dataStream> interpreted according to the conversion
+     control string in the Stream <format>.  <format> is assumed to
+     be positioned just past a $%, and a complete control string is
+     assumed available.    
+
+     Return when the conversion control string is consumed.  Leave
+     <format> pointing past the last character in the conversion
+     control string, leave <dataStream> pointing past any width
+     specified in <format>, or at the first character that doesn't
+     make sense for the <format>."
+    
+    |final width char pos data scanset exclusive return last|
+
+    final := [:retval | 
+            collection add:retval.
+            data == dataStream ifFalse:[
+                dataStream position:dataStream position + data position
+            ].
+            ^ self
+        ].
+    width := 0.
+    char := format peek.
+    char == $% ifTrue:[
+        ^ dataStream peekFor:char
+    ].
+    char == $* ifTrue:[
+        format next.
+        char := format peek.
+        final := [:retval | 
+                data == dataStream ifFalse:[
+                    dataStream position:dataStream position + data position
+                ].
+                ^ self
+            ]
+    ].
+    char isDigit ifTrue:[
+        width := Integer readFrom:format.
+        char := format peek
+    ].
+    ('slhduoxfeg' includes:char) ifTrue:[
+        dataStream skipSeparators
+    ].
+    width = 0 ifTrue:[
+        data := dataStream
+    ] ifFalse:[
+        pos := dataStream position.
+        data := ReadStream on:(dataStream next:width).
+        dataStream position:pos
+    ].
+    char == $s ifTrue:[
+        final value:(data upToSeparator)
+    ].
+    char == $c ifTrue:[
+        width = 0 ifTrue:[
+            final value:(String with:data next)
+        ] ifFalse:[
+            final value:data contents
+        ]
+    ].
+    char == $[ ifTrue:[
+        "What a mess!!!!" 
+        return := WriteStream on:(String new:8).
+        scanset := IdentitySet new.
+        format next.
+        width = 0 ifTrue:[
+            width := SmallInteger maxVal
+        ].
+        exclusive := format peekFor:$^.
+        [
+            last := char.
+            char := format next.
+            char == $]
+        ] whileFalse:[
+            char == $- ifFalse:[
+                scanset add:char
+            ] ifTrue:[
+                (last to:format next) do:[:c | 
+                    scanset add:c
+                ]
+            ]
+        ].
+        [
+            data atEnd not and:[ (scanset includes:data peek) xor:exclusive ]
+        ] whileTrue:[ return nextPut:data next ].
+        final value:return contents
+    ].
+    ('lh' includes:char) ifTrue:[
+        format next.
+        char := format peek
+    ].
+    ('DUdu' includes:char) ifTrue:[
+        final value:(Integer readFrom:data)
+    ].
+    ('FEGfeg' includes:char) ifTrue:[
+        final value:(Float readFrom:data)
+    ].
+    ('Oo' includes:char) ifTrue:[
+        final value:(Integer readFrom:data radix:8)
+    ].
+    ('Xx' includes:char) ifTrue:[
+        final value:(Integer readFrom:data radix:16)
+    ]
+!
+
+scanf:formatString fromStream:dataStream 
+    "Return a Collection of objects found in the Character Stream
+     <dataStream> as interpreted according to the receiver.  The
+     receiver is assumed to be a conversion control string as
+     specified in the UTek manual page for scanf(3)."
+    
+    |results format char|
+
+    results := OrderedCollection new.
+    format := ReadStream on:formatString.
+    [ format atEnd ] whileFalse:[
+        char := format next.
+        (char == Character space or:[ char == Character tab ]) ifTrue:[
+            dataStream skipSeparators.
+            format skipSeparators
+        ].
+        char == $% ifTrue:[
+            self 
+                scanArgFrom:dataStream
+                to:results
+                format:format
+        ] ifFalse:[
+            dataStream peekFor:char
+        ]
+    ].
+    ^ results
+!
+
+sscanf:formatString fromString:aString 
+    "Return a Collection of objects found in <string> as
+     interpreted according to the receiver.  The receiver is
+     assumed to be a conversion control string as specified in the
+     UTek manual page for scanf(3)."
+    
+    ^ self scanf:formatString fromStream:(ReadStream on:aString)
+
+    "
+     self new sscanf:'%d %x' fromString:'1234 affe'
+    "
+! !
+
+!PrintfScanf class methodsFor:'documentation'!
+
+version
+    ^ '$Header: /cvs/stx/stx/libbasic2/PrintfScanf.st,v 1.1 2004-07-27 08:28:19 cg Exp $'
+! !