#QUALITY by cg
class: PrintfScanf class
added: #format
comment/format in: #absDecimalPrintFloat:on:digits:
changed: #printArgFrom:to:arguments:
check for valid *-argument
--- a/PrintfScanf.st Tue Jun 20 14:17:14 2017 +0200
+++ b/PrintfScanf.st Tue Jun 20 17:59:59 2017 +0200
@@ -115,6 +115,113 @@
'%f\n' printf:{ 1234.0 asLongFloat } on:Transcript
'%f\n' printf:{ 1234.0 asQDouble } on:Transcript
"
+!
+
+format
+"
+ Format specifier:
+
+ required: leading '%'
+ optional: '-' (POSIX refers to this as the <<flags>>)
+ optional: positive number or '*' (POSIX <<width>>)
+ optional: period followed by positive number or * (POSIX <<precision>>)
+ optional: an h or l to indicate size of data (POSIX <<length>>)
+ required: character describing output behavior (POSIX <<conversion specifier>>)
+
+ Various implementations of printf have added different functionality.
+
+ ANSI standards up through C99:
+
+ more flags '+' ' ' '0' '#'
+ more lengths 'L' 'hh' 'll' 'j' 'z' 't'
+ more conversions 'F' 'a' 'A' 'n'
+
+ The POSIX specification of printf added:
+
+ positional parameters to identify argument indices
+ more flags ''' (single quote)
+ more conversions 'C' 'S'
+ clarifications regarding corner cases and 'undefined behavior'
+
+ BSD implementations added:
+
+ more lengths 'q'
+ more conversions 'D' 'U' 'O'
+
+ glibc (GNU) added:
+
+ more lengths 'Z'
+ more conversions 'm'
+
+ Windows C Runtime (CRT) added:
+
+ more lengths 'I' 'I32' 'I64' 'w'
+
+ glibc and CRT both added 'Z'.
+ glibc uses 'Z' for the length size_t.
+ CRT uses Z as a conversion for length-prefixed strings.
+ This implementation takes the former approach, handling 'Z' in the same way as 'z'.
+
+ BSD and IBM C library both added 'D'.
+ BSD uses D as a conversion, namely as an alias of 'ld'.
+ IBM uses 'D' for the length for _Decimal64, a decimal floating point type, in accordance with ISO/IEC TR 24732.
+ This implementation takes the former approach.
+
+ This implementation also adds new conversions:
+
+ 'b' and 'B' for binary (base-2) integer renderings
+ 'y' and 'Y' for true/false and yes/no Boolean conversions
+ 'J' for JSON
+ 'T' and 'V' for JS typeof and valueOf inspection
+
+ Conversions (upper case same as lower case):
+ 'a' (not implemented) base-2 floating point exp form
+ 'b' binary (base 2)
+ 'c' character or (first char of string)
+ 'd' character or (first char of string)
+ 'e' base-10 floating point exp form (scientific)
+ 'f' base-10 floating point decimal form (non-scientific)
+ 'g' 'e' or 'f', whichever looks more appropriate (based on value)
+ 'i' integer (alias for 'd')
+ 'j' (not implemented) JSON format
+ 'n' (not implemented) stores number of characters written into arg
+ 'o' base-8 octal
+ 'p' (not implemented) pointer
+ 's' string
+ 't' type (i.e. class name)
+ 'u' (not implemented) unsigned (negative values are converted)
+ 'v' (not implemented) store string
+ 'x' base-16 hex
+
+ Parameter selection (not implemented):
+
+ <n>$ take n'th parameter
+
+ Dynamic width/precision (consumed in order as presented):
+
+ * take width/parameter from next argument
+
+ PrintfScanf printf:'|%s|' arguments:{ 'abc' } -> '|abc|'
+ PrintfScanf printf:'|%5s|' arguments:{ 'abc' } -> '| abc|'
+ PrintfScanf printf:'|%*s|' arguments:{ 5 . 'abc' } -> '| abc|'
+
+ PrintfScanf printf:'|%8f|' arguments:{ 1.234 } -> '| 1.234|'
+ PrintfScanf printf:'|%*f|' arguments:{ 8 . 1.234 } -> '| 1.234|'
+
+
+ Negative width will fill at the right:
+
+ PrintfScanf printf:'|%5s|' arguments:{ 'abc' } -> '| abc|'
+ PrintfScanf printf:'|%-5s|' arguments:{ 'abc' } -> '|abc |'
+ PrintfScanf printf:'|%-*s|' arguments:{ 5 . 'abc' } -> '|abc |'
+ PrintfScanf printf:'|%*s|' arguments:{ -5 . 'abc' } -> '|abc |'
+
+ PrintfScanf printf:'|%*f|' arguments:{ -8 . 1.234 } -> '|1.234 |'
+ PrintfScanf printf:'|%-8f|' arguments:{ 1.234 } -> '|1.234 |'
+ PrintfScanf printf:'|%-*f|' arguments:{ 8 . 1.234 } -> '|1.234 |'
+ PrintfScanf printf:'|%-*f|' arguments:{ -8 . 1.234 } -> '|1.234 |'
+
+"
! !
!PrintfScanf class methodsFor:'instance creation'!
@@ -165,6 +272,8 @@
^ outStream nextPut: formatStream next
].
+ "/ flags:
+
char == $- ifTrue:[
ljust := true.
formatStream next.
@@ -189,23 +298,39 @@
char := formatStream peek
].
+ "/ possibly a width
char == $* ifTrue:[
- width := nextArg value.
+ width := nextArg value.
+ width isInteger ifFalse:[
+ self error:'non integer width argument in printf'
+ ].
+ width < 0 ifTrue:[
+ ljust := true.
+ width := width negated
+ ].
formatStream next.
char := formatStream peek
].
char isDigit ifTrue:[
char == $0 ifTrue: [pad := $0].
- width := Integer readFrom: formatStream.
+ width := Integer readFrom: formatStream onError:0.
char := formatStream peek
].
+ "/ precision separator
+
char == $. ifTrue:[
formatStream next. char := formatStream peek.
- char == $*
- ifTrue: [precision := nextArg value. formatStream next.]
- ifFalse: [precision := Integer readFrom: formatStream.].
+ char == $* ifTrue: [
+ precision := nextArg value.
+ precision isInteger ifFalse:[
+ self error:'non integer precision argument in printf'
+ ].
+ formatStream next.
+ ] ifFalse: [
+ precision := Integer readFrom: formatStream.
+ ].
char := formatStream peek
].
@@ -297,7 +422,7 @@
ljust ifFalse: [outStream nextPutAll: (arg copyFrom: 1 to: precision)].
^ formatStream next
- "Modified (comment): / 19-06-2017 / 15:46:33 / cg"
+ "Modified: / 20-06-2017 / 15:26:02 / cg"
!
printf:formatString argument:arg
@@ -478,10 +603,13 @@
self printf:'%20.10f\n' on:Transcript arguments: { 0.0 asLongFloat log10 }
self printf:'%20.10f\n' on:Transcript arguments: { 0.0 asQDouble log10 }
- self printf:'%20.10f\n' on:Transcript arguments: { -1.0 log10 }
- self printf:'%20.10f\n' on:Transcript arguments: { -1.0 asShortFloat log10 }
- self printf:'%20.10f\n' on:Transcript arguments: { -1.0 asLongFloat log10 }
- self printf:'%20.10f\n' on:Transcript arguments: { -1.0 asQDouble log10 }
+ self printf:'%20.10f\n' on:Transcript arguments: { DomainError ignoreIn:[ -1.0 log10 ] }
+ self printf:'%20.10f\n' on:Transcript arguments: { DomainError ignoreIn:[ -1.0 asShortFloat log10 ] }
+ self printf:'%20.10f\n' on:Transcript arguments: { DomainError ignoreIn:[ -1.0 asLongFloat log10] }
+ self printf:'%20.10f\n' on:Transcript arguments: { DomainError ignoreIn:[ -1.0 asQDouble log10] }
+
+ self printf:'%10s\n' on:Transcript arguments:{ 'hello' }
+ self printf:'%*s\n' on:Transcript arguments:{ 10 . 'hello' }
"
|absVal exp x fuzz i|
@@ -546,7 +674,7 @@
]
]
- "Modified (comment): / 20-06-2017 / 13:33:40 / cg"
+ "Modified (comment): / 20-06-2017 / 15:00:22 / cg"
!
absPrintFloat:aFloat on:aStream digits:digits