challenge printfの自作

printf関数を自作してください。
printfの説明は不要だと思います。とりあえずWikiPediaのリンクをはっておきます。

実際にはsprintf関数を作ってください。
注意事項
  • 標準でついているprintf系関数の使用禁止
  • 標準でついているライブラリ以外の使用禁止
  • 引数・返り値等の仕様はできるだけ似せればよい

可変長引数など、言語によっては難しい/不可能な仕様もありますが、いろいろ工夫して本物に近づくようにしてみてください。
1
2
3
4
5
6
7
#include <string.h>

// なにもフォーマットしてない
int mysprintf(char *str, const char *format, ... ){
    strcpy(str, format);
    return strlen(str);
}

Posted feedbacks - Flatten

Nested Hidden
場合わけが多くなるのでなかなかきれいにまとまりませんが、仕事ではこういうベタな仕様をえいやっと書くことも多い気がするので、書いとけば参考になるかなと。

さぼったところ:
- 浮動小数点数はeEfFgGを区別せず。いい加減です。
- 変換指定子 aApnはサポートせず。
- 長さ修飾子はSchemeはサポートせず(Schemeでは意味がないかな)
- n$による引数の並べかえはサポートせず。

後はそれなりにサポートしているつもりです。
gosh> (sprintf "|%d|" 123)
"|123|"
gosh> (sprintf "|%10d|" 123)
"|       123|"
gosh> (sprintf "|%-10d|" 123)
"|123       |"
gosh> (sprintf "|%10.5d|" 123)
"|     00123|"
gosh> (sprintf "|%+10.5d|" 123)
"|    +00123|"
gosh> (sprintf "|%+10d|" 123)
"|      +123|"
gosh> (sprintf "|%+10o|" 123)
"|      +173|"
gosh> (sprintf "|%10o|" 123)
"|       173|"
gosh> (sprintf "|%#10o|" 123)
"|      0173|"
gosh> (sprintf "|%10x|" 123)
"|        7b|"
gosh> (sprintf "|%10X|" 123)
"|        7B|"
gosh> (sprintf "|%#10x|" 123)
"|      0x7b|"
gosh> (sprintf "|%f|" 3.14)
"|3.14|"
gosh> (sprintf "|%.6f|" (sqrt 2))
"|1.414214|"
gosh> (sprintf "|%10.6f|" (sqrt 2))
"|  1.414214|"
gosh> (sprintf "|%10.6f|" (- (sqrt 2)))
"| -1.414214|"
gosh> (sprintf "|%*.*f|" 10 6 (log 10))
"|  2.302585|"
gosh> (sprintf "|%f|" 10e100)
"|1.0e101|"
gosh> (sprintf "|%f|" 1e100)
"|1.0e100|"
gosh> (sprintf "|%.6f|" 1e100)
"|1.000000e100|"
gosh> (sprintf "|%s|" "abc")
"|abc|"
gosh> (sprintf "|%10s|" "abc")
"|       abc|"
gosh> (sprintf "|%-10s|" "abc")
"|abc       |"
gosh> (sprintf "|%*s|" 10 "abc")
"|       abc|"
gosh> (sprintf "|%c|" #\z)
"|z|"
gosh> (sprintf "|%%|")
"|%|"
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
(use gauche.sequence)
(use util.match)
(use text.tree)
(use srfi-1)

(define (sprintf fmt . args)
  (define (get-var-arg name v args)
    (if v
      (cond [(string->number v) => (cut values <> args)]
            [(equal? v "*")     (values (car args) (cdr args))]
            [else (error #`",name needs to be a number or '*', but got:" v)])
      (values #f args)))
  (define (pick-arg conv args)
    (if (equal? conv "%") (values #f args) (car+cdr args)))
  (define (rec fmt args)
    (rxmatch-case fmt
      [#/(.*)%([-+ #0]*)(\d+|\*)?(?:\.(\d*|\*))?([diouxXeEfFgGaAcspn%])(.*)/
       (_ before flags width prec conv after)
       (receive (width args) (get-var-arg 'width width args)
         (receive (prec args) (get-var-arg 'prec prec args)
           (receive (arg args) (pick-arg conv args)
             `(,before
               ,(fill width flags (dispatch arg prec flags conv))
               ,(rec after args)))))]
      [else fmt]))
  (define (dispatch arg prec flags conv)
    (case (ref conv 0)
      [(#\d #\i #\u) (int arg prec flags 10 conv)]
      [(#\o)         (int arg prec flags 8 conv)]
      [(#\x #\X)     (int arg prec flags 16 conv)]
      [(#\e #\E #\f #\F #\g #\G) (real arg prec flags conv)]
      [(#\c)         (if (char? arg)
                       (string arg)
                       (error "char required for %c conversion:" arg))]
      [(#\s)         (if (string? arg)
                       (if (and prec (< prec (string-length arg)))
                         (string-take arg prec)
                         arg)
                       (error "string required for %s conversion:" arg))]
      [(#\%)         "%"]
      [else          (error "unsupported conversion:" conv)]))
  (define (fill w f s)
    (or (and-let* ([ w ]
                   [len (string-length s)]
                   [(< len w)]
                   [pad (- w len)])
          (if (string-index f #\-)
            (cons s (make-string pad #\space))
            (cons (make-string pad (if (string-index f #\0) #\0 #\space)) s)))
        s))
  (define (sign arg f s)
    (define (pos-sign)
      (cond [(string-index f #\+) "+"]
            [(string-index f #\space) " "]
            [else ""]))
    (string-append (if (negative? arg) "-" (pos-sign)) s))
  (define (minpad p s)
    (if (and p (< (string-length s) p))
      (string-append (make-string (- p (string-length s)) #\0) s)
      s))
  (define (prefix f c s)
    (if (string-index f #\#)
      (case (ref c 0)
        [(#\o) (string-append "0" s)]

        [(#\x) (string-append "0x" s)]
        [(#\X) (string-append "0X" s)])
      s))
  (define (int arg p f r c)
    (unless (and (exact? arg) (integer? arg))
      (error "exact integer required for conversion:" c))
    (sign arg f (prefix f c (minpad p (number->string (abs arg) r (equal? c "X"))))))
  (define (maxprec p s)
    (cond [(not p) s]
          [else
           (regexp-replace*
            s #/\.(\d+)/
            (lambda (m)
              (cond [(zero? p) ""]
                    [else
                     (let1 oprec (string-length (m 1))
                       (cond [(< p oprec)
                              #`".,(round->exact (/. (string->number (m 1)) (expt 10 (- oprec p))))"]
                             [else
                              #`".,(m 1),(make-string (- p oprec) #\\0)"]))
                     ])))
           ]))
  (define (real arg p f c)
    (unless (real? arg)
      (error "real number required for conversion:" c))
    (maxprec p (number->string arg)))
  (tree->string (rec fmt args)))

しまった。複数のフォーマット指示子がある場合にちゃんと動きませんでした。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
*** t.scm    2007-11-25 01:51:56.000000000 -1000
--- t2.scm    2007-11-25 01:53:52.000000000 -1000
***************
*** 13,28 ****
    (define (pick-arg conv args)
      (if (equal? conv "%") (values #f args) (car+cdr args)))
    (define (rec fmt args)
!     (rxmatch-case fmt
!       [#/(.*)%([-+ #0]*)(\d+|\*)?(?:\.(\d*|\*))?([diouxXeEfFgGaAcspn%])(.*)/
!        (_ before flags width prec conv after)
!        (receive (width args) (get-var-arg 'width width args)
!          (receive (prec args) (get-var-arg 'prec prec args)
!            (receive (arg args) (pick-arg conv args)
!              `(,before
!                ,(fill width flags (dispatch arg prec flags conv))
!                ,(rec after args)))))]
!       [else fmt]))
    (define (dispatch arg prec flags conv)
      (case (ref conv 0)
        [(#\d #\i #\u) (int arg prec flags 10 conv)]
--- 13,31 ----
    (define (pick-arg conv args)
      (if (equal? conv "%") (values #f args) (car+cdr args)))
    (define (rec fmt args)
!     (receive (pre post) (string-scan fmt #\% 'both)
!       (if post
!         (rxmatch-case post
!           [#/([-+ #0]*)(\d+|\*)?(?:\.(\d*|\*))?([diouxXeEfFgGaAcspn%])(.*)/
!            (_ flags width prec conv after)
!            (receive (width args) (get-var-arg 'width width args)
!              (receive (prec args) (get-var-arg 'prec prec args)
!                (receive (arg args) (pick-arg conv args)
!                  `(,pre
!                    ,(fill width flags (dispatch arg prec flags conv))
!                    ,(rec after args)))))]
!           [else (error "bad format string:" fmt)])
!         fmt)))
    (define (dispatch arg prec flags conv)
      (case (ref conv 0)
        [(#\d #\i #\u) (int arg prec flags 10 conv)]

Haskellの場合,可変長引数の扱いに工夫が必要です.

下手なコードを書くより,ghcのライブラリを読んで実際にどうしているかを見る方が勉強になると思うので,ライブラリのコード(Text.Printfモジュール)をそのまま掲載します.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
-----------------------------------------------------------------------------
-- |
-- Module      :  Text.Printf
-- Copyright   :  (c) Lennart Augustsson, 2004
-- License     :  BSD-style (see the file libraries/base/LICENSE)
-- 
-- Maintainer  :  lennart@augustsson.net
-- Stability   :  provisional
-- Portability :  portable
--
-- A C printf like formatter.
--
-----------------------------------------------------------------------------

module Text.Printf(
   printf, hPrintf,
   PrintfType, HPrintfType, PrintfArg, IsChar
) where

import Prelude
import Data.Char
import Data.Int
import Data.Word
import Numeric(showEFloat, showFFloat, showGFloat)
import System.IO

-------------------

-- | Format a variable number of arguments with the C-style formatting string.
-- The return value is either 'String' or @('IO' a)@.
--
-- The format string consists of ordinary characters and /conversion
-- specifications/, which specify how to format one of the arguments
-- to printf in the output string.  A conversion specification begins with the
-- character @%@, followed by one or more of the following flags:
--
-- >    -      left adjust (default is right adjust)
-- >    +      always use a sign (+ or -) for signed conversions
-- >    0      pad with zeroes rather than spaces
--
-- followed optionally by a field width:
-- 
-- >    num    field width
-- >    *      as num, but taken from argument list
--
-- followed optionally by a precision:
--
-- >    .num   precision (number of decimal places)
--
-- and finally, a format character:
--
-- >    c      character               Char, Int, Integer, ...
-- >    d      decimal                 Char, Int, Integer, ...
-- >    o      octal                   Char, Int, Integer, ...
-- >    x      hexadecimal             Char, Int, Integer, ...
-- >    X      hexadecimal             Char, Int, Integer, ...
-- >    u      unsigned decimal        Char, Int, Integer, ...
-- >    f      floating point          Float, Double
-- >    g      general format float    Float, Double
-- >    G      general format float    Float, Double
-- >    e      exponent format float   Float, Double
-- >    E      exponent format float   Float, Double
-- >    s      string                  String
--
-- Mismatch between the argument types and the format string will cause
-- an exception to be thrown at runtime.
--
-- Examples:
--
-- >   > printf "%d\n" (23::Int)
-- >   23
-- >   > printf "%s %s\n" "Hello" "World"
-- >   Hello World
-- >   > printf "%.2f\n" pi
-- >   3.14
--
printf :: (PrintfType r) => String -> r
printf fmts = spr fmts []

-- | Similar to 'printf', except that output is via the specified
-- 'Handle'.  The return type is restricted to @('IO' a)@.
hPrintf :: (HPrintfType r) => Handle -> String -> r
hPrintf hdl fmts = hspr hdl fmts []

-- |The 'PrintfType' class provides the variable argument magic for
-- 'printf'.  Its implementation is intentionally not visible from
-- this module. If you attempt to pass an argument of a type which
-- is not an instance of this class to 'printf' or 'hPrintf', then
-- the compiler will report it as a missing instance of 'PrintfArg'.
class PrintfType t where
    spr :: String -> [UPrintf] -> t

-- | The 'HPrintfType' class provides the variable argument magic for
-- 'hPrintf'.  Its implementation is intentionally not visible from
-- this module.
class HPrintfType t where
    hspr :: Handle -> String -> [UPrintf] -> t

{- not allowed in Haskell 98
instance PrintfType String where
    spr fmt args = uprintf fmt (reverse args)
-}
instance (IsChar c) => PrintfType [c] where
    spr fmts args = map fromChar (uprintf fmts (reverse args))

instance PrintfType (IO a) where
    spr fmts args = do
    putStr (uprintf fmts (reverse args))
    return undefined

instance HPrintfType (IO a) where
    hspr hdl fmts args = do
    hPutStr hdl (uprintf fmts (reverse args))
    return undefined

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
    spr fmts args = \ a -> spr fmts (toUPrintf a : args)

instance (PrintfArg a, HPrintfType r) => HPrintfType (a -> r) where
    hspr hdl fmts args = \ a -> hspr hdl fmts (toUPrintf a : args)

class PrintfArg a where
    toUPrintf :: a -> UPrintf

instance PrintfArg Char where
    toUPrintf c = UChar c

{- not allowed in Haskell 98
instance PrintfArg String where
    toUPrintf s = UString s
-}
instance (IsChar c) => PrintfArg [c] where
    toUPrintf = UString . map toChar

instance PrintfArg Int where
    toUPrintf = uInteger

instance PrintfArg Int8 where
    toUPrintf = uInteger

instance PrintfArg Int16 where
    toUPrintf = uInteger

instance PrintfArg Int32 where
    toUPrintf = uInteger

instance PrintfArg Int64 where
    toUPrintf = uInteger

#ifndef __NHC__
instance PrintfArg Word where
    toUPrintf = uInteger
#endif

instance PrintfArg Word8 where
    toUPrintf = uInteger

instance PrintfArg Word16 where
    toUPrintf = uInteger

instance PrintfArg Word32 where
    toUPrintf = uInteger

instance PrintfArg Word64 where
    toUPrintf = uInteger

instance PrintfArg Integer where
    toUPrintf = UInteger 0

instance PrintfArg Float where
    toUPrintf = UFloat

instance PrintfArg Double where
    toUPrintf = UDouble

uInteger :: (Integral a, Bounded a) => a -> UPrintf
uInteger x = UInteger (toInteger $ minBound `asTypeOf` x) (toInteger x)

class IsChar c where
    toChar :: c -> Char
    fromChar :: Char -> c

instance IsChar Char where
    toChar c = c
    fromChar c = c

-------------------

data UPrintf = UChar Char | UString String | UInteger Integer Integer | UFloat Float | UDouble Double

uprintf :: String -> [UPrintf] -> String
uprintf ""       []       = ""
uprintf ""       (_:_)    = fmterr
uprintf ('%':'%':cs) us   = '%':uprintf cs us
uprintf ('%':_)  []       = argerr
uprintf ('%':cs) us@(_:_) = fmt cs us
uprintf (c:cs)   us       = c:uprintf cs us

fmt :: String -> [UPrintf] -> String
fmt cs us =
    let (width, prec, ladj, zero, plus, cs', us') = getSpecs False False False cs us
        adjust (pre, str) = 
        let lstr = length str
            lpre = length pre
            fill = if lstr+lpre < width then take (width-(lstr+lpre)) (repeat (if zero then '0' else ' ')) else ""
        in  if ladj then pre ++ str ++ fill else if zero then pre ++ fill ++ str else fill ++ pre ++ str
            adjust' ("", str) | plus = adjust ("+", str)
            adjust' ps = adjust ps
        in
    case cs' of
    []     -> fmterr
    c:cs'' ->
        case us' of
        []     -> argerr
        u:us'' ->
        (case c of
        'c' -> adjust  ("", [toEnum (toint u)])
        'd' -> adjust' (fmti u)
        'i' -> adjust' (fmti u)
        'x' -> adjust  ("", fmtu 16 u)
        'X' -> adjust  ("", map toUpper $ fmtu 16 u)
        'o' -> adjust  ("", fmtu 8  u)
        'u' -> adjust  ("", fmtu 10 u)
        'e' -> adjust' (dfmt' c prec u)
        'E' -> adjust' (dfmt' c prec u)
        'f' -> adjust' (dfmt' c prec u)
        'g' -> adjust' (dfmt' c prec u)
        'G' -> adjust' (dfmt' c prec u)
        's' -> adjust  ("", tostr u)
        _   -> perror ("bad formatting char " ++ [c])
         ) ++ uprintf cs'' us''

fmti :: UPrintf -> (String, String)
fmti (UInteger _ i) = if i < 0 then ("-", show (-i)) else ("", show i)
fmti (UChar c)      = fmti (uInteger (fromEnum c))
fmti _            = baderr

fmtu :: Integer -> UPrintf -> String
fmtu b (UInteger l i) = itosb b (if i < 0 then -2*l + i else i)
fmtu b (UChar c)      = itosb b (toInteger (fromEnum c))
fmtu _ _              = baderr

toint :: UPrintf -> Int
toint (UInteger _ i) = fromInteger i
toint (UChar c)      = fromEnum c
toint _             = baderr

tostr :: UPrintf -> String
tostr (UString s) = s
tostr _          = baderr

itosb :: Integer -> Integer -> String
itosb b n = 
    if n < b then 
        [intToDigit $ fromInteger n]
    else
        let (q, r) = quotRem n b in
        itosb b q ++ [intToDigit $ fromInteger r]

stoi :: Int -> String -> (Int, String)
stoi a (c:cs) | isDigit c = stoi (a*10 + digitToInt c) cs
stoi a cs                 = (a, cs)

getSpecs :: Bool -> Bool -> Bool -> String -> [UPrintf] -> (Int, Int, Bool, Bool, Bool, String, [UPrintf])
getSpecs _ z s ('-':cs) us = getSpecs True z s cs us
getSpecs l z _ ('+':cs) us = getSpecs l z True cs us
getSpecs l _ s ('0':cs) us = getSpecs l True s cs us
getSpecs l z s ('*':cs) us = 
        case us of
        [] -> argerr
        nu : us' ->
        let n = toint nu
        (p, cs'', us'') =
            case cs of
                    '.':'*':r -> case us' of { [] -> argerr; pu:us''' -> (toint pu, r, us''') }
            '.':r     -> let (n', cs') = stoi 0 r in (n', cs', us')
            _         -> (-1, cs, us')
        in  (n, p, l, z, s, cs'', us'')
getSpecs l z s ('.':cs) us =
    let (p, cs') = stoi 0 cs
    in  (0, p, l, z, s, cs', us)
getSpecs l z s cs@(c:_) us | isDigit c =
    let (n, cs') = stoi 0 cs
        (p, cs'') = case cs' of
            '.':r -> stoi 0 r
            _     -> (-1, cs')
    in  (n, p, l, z, s, cs'', us)
getSpecs l z s cs       us = (0, -1, l, z, s, cs, us)

dfmt' :: Char -> Int -> UPrintf -> (String, String)
dfmt' c p (UDouble d) = dfmt c p d
dfmt' c p (UFloat f)  = dfmt c p f
dfmt' _ _ _           = baderr

dfmt :: (RealFloat a) => Char -> Int -> a -> (String, String)
dfmt c p d =
    case (if isUpper c then map toUpper else id) $
             (case toLower c of
                  'e' -> showEFloat
                  'f' -> showFFloat
                  'g' -> showGFloat
                  _   -> error "Printf.dfmt: impossible"
             )
               (if p < 0 then Nothing else Just p) d "" of
    '-':cs -> ("-", cs)
    cs     -> ("" , cs)

perror :: String -> a
perror s = error ("Printf.printf: "++s)
fmterr, argerr, baderr :: a
fmterr = perror "formatting string ended prematurely"
argerr = perror "argument list ended prematurely"
baderr = perror "bad argument"

仕様削ってもいいから、シンプルに実装しろとかなら良かったのに。
組み込みとかの厳しい環境だと、printfの関数を入れると、かなりのメモリが圧迫されるケースがあるわけで、そういう奴用に。

前に作ったやつがあるので恥ずかしながら投げてみます。
shiroさんがきっと書いてくれると思ったのでshiroさんの投稿を待った事をここに告白します。
ライブラリにntというディレクトリを作ってその下に置き、
(use nt.printf) して使います。

Gauche 0.8.12で動きます。0.8.11以前では正規表現にmatchしなかった場合に#fではなく""が返るので一部修正が必要です。
なお、104行目の (d o x X) のところを (d b o x X) にすると、%bで2進表記をするオレオレ拡張が入ります。

テストコードも以下に付けておきます。
;;; nt/test/printf.scm
(use gauche.test)

(test-start "nt.printf")
(use nt.printf)
(test-module 'nt.printf)

(test-section "escaped symbols")
(test* "\\t" "\t" (sprintf "\t"))
(test* "\\n" "\n" (sprintf "\n"))
(test* "\\\"" "\"" (sprintf "\""))
(test* "\\\\" "\\" (sprintf "\\"))
(test* "%%" "%" (sprintf "%%"))

(test-section "%d")
(test* "%d 1" "1" (sprintf "%d" 1))
(test* "%3d 1" "  1" (sprintf "%3d" 1))
(test* "%3d 1111" "1111" (sprintf "%3d" 1111))
(test* "%03d 1" "001" (sprintf "%03d" 1))
(test* "%-3d" "1  " (sprintf "%-3d" 1))
(test* "%+d 1" "+1" (sprintf "%+d" 1))
(test* "%+3d 1" " +1" (sprintf "%+3d" 1))

(test* "%d zero" "0" (sprintf "%d" 0))

(test* "%d minusvalue" "-1" (sprintf "%d" -1))
(test* "%3d minusvalue" " -1" (sprintf "%3d" -1))
;(test* "%03d minusvalue" "-01" (sprintf "%03d" -1))
(test* "%-d minusvalue" "-1" (sprintf "%-d" -1))
(test* "%+d minusvalue" "-1" (sprintf "%+d" -1))
(test* "%+3d minusvalue" " -1" (sprintf "%+3d" -1))

(test-section "%d with non-integer values")
(test* "%d float 2.71828" "2" (sprintf "%d" 2.71828))
(test* "%d rational 3/2" "1" (sprintf "%d" 3/2))
(test* "%d string \"123\"" "123" (sprintf "%d" "123"))
(test* "%d string \"abc\"" "0" (sprintf "%d" "abc"))
(test* "%d symbol '1" "1" (sprintf "%d" '1))
(test* "%d symbol 'a" "0" (sprintf "%d" 'a))

(test-section "%i %u")
(test* "%i" "999" (sprintf "%i" 999))
(test* "%u" "999" (sprintf "%u" 999))

(test-section "%x %X")
(test* "%x 15" "f" (sprintf "%x" 15))
(test* "%3x 15" "  f" (sprintf "%3x" 15))
(test* "%03x 15" "00f" (sprintf "%03x" 15))
(test* "%03x 65535" "ffff" (sprintf "%03x" 65535))
;(test* "%-3x 15" "f  " (sprintf "%-3x" 15))

(test* "%X 15" "F" (sprintf "%X" 15))
(test* "%3X 15" "  F" (sprintf "%3X" 15))
(test* "%03X 15" "00F" (sprintf "%03X" 15))
(test* "%03X 65535" "FFFF" (sprintf "%03X" 65535))
;(test* "%-3x 15" "f  " (sprintf "%-3x" 15))

(test-section "%o")
(test* "%o 9" "11" (sprintf "%o" 9))
(test* "%3o 9" " 11" (sprintf "%3o" 9))
(test* "%03o 9" "011" (sprintf "%03o" 9))
(test* "%3o 255" "377" (sprintf "%3o" 255))
(test* "%3o 511" "777" (sprintf "%3o" 511))
(test* "%3o 585" "1111" (sprintf "%3o" 585))
;(test* "%-3o" "11 " (sprintf "%-3o" 9))

;(test-section "%b") ; original feature
;(test* "%b 2" "10" (sprintf "%b" 2))
;(test* "%3b 2" " 10" (sprintf "%3b" 2))
;(test* "%03b 2" "010" (sprintf "%03b" 2))
;(test* "%3b 15" "1111" (sprintf "%3b" 15))
;;(test* "%-3b 2" "10 " (sprintf "%-3b" 3))

(test-section "%c")
(test* "%c 9" "\t" (sprintf "%c" 9))
(test* "%c 13" "\r" (sprintf "%c" 13))
(test* "%c 32" " " (sprintf "%c" #x20)) ; 32
(test* "%c 55" "7" (sprintf "%c" #x37)) ; 55
(test* "%c 69" "E" (sprintf "%c" #x45)) ; 69
(test* "%c 12354" "あ" (sprintf "%c" #x3042)) ; = 12354

(test* "%c \"abc\"" "a" (sprintf "%c" "a"))
(test* "%c \"いろは\"" "い" (sprintf "%c" "いろは"))

(test-section "%s")
(test* "%s" "\n" (sprintf "%s" "\n"))
(test* "%s" "a" (sprintf "%s" "a"))
(test* "%3s" "   " (sprintf "%3s" ""))
(test* "%3s" "  a" (sprintf "%3s" "a"))
(test* "%3s" " aa" (sprintf "%3s" "aa"))
(test* "%3s" "aaa" (sprintf "%3s" "aaa"))
(test* "%3s" "aaaa" (sprintf "%3s" "aaaa"))
(test* "%-3s" "   " (sprintf "%-3s" ""))
(test* "%-3s" "a  " (sprintf "%-3s" "a"))
(test* "%-3s" "aa " (sprintf "%-3s" "aa"))
(test* "%-3s" "aaa" (sprintf "%-3s" "aaa"))
(test* "%-3s" "aaaa" (sprintf "%-3s" "aaaa"))

(test-section "%s with non-string values")
(test* "%s integer 5" "5" (sprintf "%s" 5))
(test* "%s float 3.14" "3.14" (sprintf "%s" 3.14))
(test* "%s rational 3/2" "3/2" (sprintf "%s" 3/2))
(test* "%s symbol 'abc" "abc" (sprintf "%s" 'abc))
(test* "%s list (1 2 3)" "(1 2 3)" (sprintf "%s" '(1 2 3)))
(test* "%s empty list ()" "()" (sprintf "%s" '()))
(test* "%s dotted list (1 . 2)" "(1 . 2)" (sprintf "%s" '(1 . 2)))
(test* "%s #t" "#t" (sprintf "%s" #t))
(test* "%s #f" "#f" (sprintf "%s" #f))

(test-section "%f")
(test* "%f" "3.140000" (sprintf "%f" 3.14))
(test* "%f" "3.141593" (sprintf "%f" 3.1415926))

(test* "%f" "-3.140000" (sprintf "%f" -3.14))
(test* "%f" "-3.141593" (sprintf "%f" -3.1415926))

(test* "%.0f" "3" (sprintf "%.0f" 3.14))
(test* "%.1f" "3.1" (sprintf "%.1f" 3.14))
(test* "%.2f" "3.14" (sprintf "%.2f" 3.14))
(test* "%.3f" "3.140" (sprintf "%.3f" 3.14))
(test* "%.4f" "3.1400" (sprintf "%.4f" 3.14))

(test* "%1.0f 3.14" "3" (sprintf "%1.0f" 3.14))
(test* "%1.1f 3.14" "3.1" (sprintf "%1.1f" 3.14))
(test* "%1.2f 3.14" "3.14" (sprintf "%1.2f" 3.14))
(test* "%1.0f 3.15" "3" (sprintf "%1.0f" 3.15))
(test* "%1.1f 3.15" "3.2" (sprintf "%1.1f" 3.15))
(test* "%1.2f 3.15" "3.15" (sprintf "%1.2f" 3.15))

(test* "%2.0f" " 3" (sprintf "%2.0f" 3.14))
(test* "%2.1f" "3.1" (sprintf "%2.1f" 3.14))
(test* "%2.2f" "3.14" (sprintf "%2.2f" 3.14))
(test* "%-2.0f" "3 " (sprintf "%-2.0f" 3.14))
(test* "%-2.1f" "3.1" (sprintf "%-2.1f" 3.14))
(test* "%-2.2f" "3.14" (sprintf "%-2.2f" 3.14))

(test* "%3.0f" "  3" (sprintf "%3.0f" 3.14))
(test* "%3.1f" "3.1" (sprintf "%3.1f" 3.14))
(test* "%3.2f" "3.14" (sprintf "%3.2f" 3.14))
(test* "%3.3f" "3.140" (sprintf "%3.3f" 3.14))
(test* "%-3.0f" "3  " (sprintf "%-3.0f" 3.14))
(test* "%-3.1f" "3.1" (sprintf "%-3.1f" 3.14))
(test* "%-3.2f" "3.14" (sprintf "%-3.2f" 3.14))
(test* "%-3.3f" "3.140" (sprintf "%-3.3f" 3.14))

(test* "%4.0f" "   3" (sprintf "%4.0f" 3.14))
(test* "%4.1f" " 3.1" (sprintf "%4.1f" 3.14))
(test* "%4.2f" "3.14" (sprintf "%4.2f" 3.14))
(test* "%4.3f" "3.140" (sprintf "%4.3f" 3.14))
(test* "%4.4f" "3.1400" (sprintf "%4.4f" 3.14))
(test* "%-4.0f" "3   " (sprintf "%-4.0f" 3.14))
(test* "%-4.1f" "3.1 " (sprintf "%-4.1f" 3.14))
(test* "%-4.2f" "3.14" (sprintf "%-4.2f" 3.14))
(test* "%-4.3f" "3.140" (sprintf "%-4.3f" 3.14))
(test* "%-4.4f" "3.1400" (sprintf "%-4.4f" 3.14))

(test* "%5.0f" "    3" (sprintf "%5.0f" 3.14))
(test* "%5.1f" "  3.1" (sprintf "%5.1f" 3.14))
(test* "%5.2f" " 3.14" (sprintf "%5.2f" 3.14))
(test* "%5.3f" "3.140" (sprintf "%5.3f" 3.14))
(test* "%5.4f" "3.1400" (sprintf "%5.4f" 3.14))
(test* "%5.5f" "3.14000" (sprintf "%5.5f" 3.14))
(test* "%-5.0f" "3    " (sprintf "%-5.0f" 3.14))
(test* "%-5.1f" "3.1  " (sprintf "%-5.1f" 3.14))
(test* "%-5.2f" "3.14 " (sprintf "%-5.2f" 3.14))
(test* "%-5.3f" "3.140" (sprintf "%-5.3f" 3.14))
(test* "%-5.4f" "3.1400" (sprintf "%-5.4f" 3.14))
(test* "%-5.5f" "3.14000" (sprintf "%-5.5f" 3.14))

(test-section "%e %E")
(test* "%e 0.0000314" "3.140000e-05" (sprintf "%e" 0.0000314))
(test* "%e 0.000314" "3.140000e-04" (sprintf "%e" 0.000314))
(test* "%e 0.00314" "3.140000e-03" (sprintf "%e" 0.00314))
(test* "%e 0.0314" "3.140000e-02" (sprintf "%e" 0.0314))
(test* "%e 0.314" "3.140000e-01" (sprintf "%e" 0.314))
(test* "%e 3.14" "3.140000e+00" (sprintf "%e" 3.14))
(test* "%e 31.4" "3.140000e+01" (sprintf "%e" 31.4))
(test* "%e 314" "3.140000e+02" (sprintf "%e" 314))
(test* "%e 3140" "3.140000e+03" (sprintf "%e" 3140))
(test* "%e 31400" "3.140000e+04" (sprintf "%e" 31400))
(test* "%e 314000" "3.140000e+05" (sprintf "%e" 314000))
(test* "%.0e 31415926" "3e+07" (sprintf "%.0e" 31415926))
(test* "%.1e 31415926" "3.1e+07" (sprintf "%.1e" 31415926))
(test* "%.2e 31415926" "3.14e+07" (sprintf "%.2e" 31415926))
(test* "%.3e 31415926" "3.142e+07" (sprintf "%.3e" 31415926))
(test* "%.4e 31415926" "3.1416e+07" (sprintf "%.4e" 31415926))
(test* "%.5e 31415926" "3.14159e+07" (sprintf "%.5e" 31415926))

(test* "%E" "3.140000E+00" (sprintf "%E" 3.14))

(test-section "%g")
(test* "%g" "3.14" (sprintf "%g" 3.14))
(test* "%g" "-3.14" (sprintf "%g" -3.14))
(test* "%g" "3.1415926" (sprintf "%g" 3.1415926))

(test-end)
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
;;; nt/printf.scm
(define-module nt.printf
  (export
   printf
   sprintf
   ))

(select-module nt.printf)

(define (log10 x) (/ (log x) (log 10.0)))

(define (sprintf fmt . args)
  (let ((out (open-output-string)))
    (let loop ((fmt fmt) (args args))
      (define (ret) (display fmt out) (get-output-string out))
      (let ((match (#/%(?<flag>[-+# 0]*)(?<num>[1-9][0-9]*)?(\.(?<below>[0-9]+))?(?<type>[%diouxXfeEgGbcs])/ fmt)))
        (if match
            (let* ((flags (if (match 'flag) (string->list (match 'flag)) '()))
                   (num (match 'num)) ;str/指定がなければ#f. ※0.8.11までは#fではなく""が返る
                   (below (match 'below)) ;str/指定がなければ#f. ※0.8.11までは#fではなく""が返る
                   (type (string->symbol (match 'type)))
                   (consumes-an-arg? (not (eq? '% type)))
                   (flush-left? (memq #\- flags))
                   (signed? (memq #\+ flags))
                   (zero-pad? (memq #\0 flags))
                   )
              (if (and consumes-an-arg? (null? args))
                  (ret) ;;; arguments-exhausted
                  (let ((arg (if consumes-an-arg? (car args) 'not-eaten)))
                    
                    (define (format-int-value type)
                      (let ((%value (if (integer? arg) arg (x->integer (truncate (x->number arg))))))
                        (if flush-left?
                            (let ((%fmt (string-append "~" type)))
                              (format (string-append "~" (or num "") "a") (format %fmt %value)))
                            (let ((%fmt (string-append "~" (or num "") "," (if zero-pad? "'0" "")
                                                       (if signed? "@" "") type)))
                              (format %fmt %value)))))

                    (define (format-float-value); type)
                      (let* ((%value (if (real? arg) arg (x->number arg)))
                             (%e-offset (x->integer (floor (log10 (abs %value)))))
                             (%precision (case type
                                           ((f)
;                                            (if (string=? "" below) 6 (x->integer below))) ; < 0.8.12
                                            (if below (x->integer below) 6))
                                           ((e E)
                                            (cond ((> %e-offset 0) ;;; sorry i'm using (set!)
                                                   (set! %value (/ %value (expt 10 %e-offset))))
                                                  ((< %e-offset 0)
                                                   (set! %value (* %value (expt 10 (abs %e-offset)))))
                                                  (else #t))
;                                            (if (string=? "" below) 6 (x->integer below))) ;< 0.8.12
                                            (if below (x->integer below) 6))
;                                           (else (if (string=? "" below) 12 (x->integer below))))) ;< 0.8.12
                                           (else (if below (x->integer below) 12))))
                             )
                        (let* ((%int (x->integer (if (= 0 %precision)
                                                     (round %value)
                                                     (truncate %value))))
                               (%fract (abs (- %value %int)))
                               (%rounded (x->integer (round (+ (expt 0.1 (+ %precision 1))
                                                               (* (expt 10 %precision) %fract)))))
                               (%str (if (= 0 %precision)
                                         (format "~d" %int)
                                         (format (string-append "~d.~" (format "~d" %precision) ",'0d")
                                                 %int %rounded)))
                               (%f-fmt (string-append "~" (or num "") ;"," (if zero-pad? "'0" "")
                                                      (if flush-left? "" "@") "a"))
                               (%e-fmt (string-append %f-fmt
                                                      "~a" ; [eE]
                                                      (if (< %e-offset 0) "-" "+")
                                                      "~2,'0d"))
                               )
                          (case type
                            ((f)
                             (format %f-fmt %str))
                            ((g G)
                             (format %f-fmt (regexp-replace #/0+$/ %str "")))
                            ((e E)
                             (format %e-fmt %str type (abs %e-offset))
                             )
                            ))))
                    
                    (display (match 'before) out)
                    ;; warnings
;                    (case type
;                      ((d i)
;                       (when (not (integer? arg))
;                             (print "warning: %~a requires <integer>" type) ))
;                      ((b o u X x c)
;                       (when (not (and (integer? arg) (< 0 arg)))
;                             (print "warning: %~a requires <unsigned integer>" type) ))
;                      ((f e E g G)
;                       (when (not (real? arg))
;                             (print "warning: %~a requires <real>" type)))
;;                      ((b)) ; we use %b for unsigned binary
;                      ((s)
;                       (when (not (string? arg))
;                             (format "warning: %s requires <string>")))
;                      )
                    (display (case type
                               ((d o x X) ;;  %bを2進表記に使いたい場合は (d b o x X)
                                (format-int-value (match 'type)))
                               ((i u) ; signed/unsigned decimal
                                (format-int-value "d"))
                               ((f) ;float ; [-]ddd.ddd
                                (format-float-value))
                               ((e E) ;'not-supported) ; [-]d.ddde+-dd
                                (format-float-value))
                               ((g G) ; 'not-supported) ;
                                (format-float-value))
;                               ((b) 'not-supported) ; backslash-escape seq
                               ((c) ; first-char
                                (cond ((integer? arg)
                                       (string (integer->char arg)))
                                      ((string? arg)
                                       (string-ref arg 0))
                                      (else 
                                       (string (integer->char (truncate (x->number arg))))) ))
                               ((s) ; 'str
                                (let1 %s-fmt (string-append "~"
                                                            (format "~a~a"
                                                                    (or num "")
                                                                    (if flush-left? "" "@")
                                                                    )
                                                            "a")
                                      (format %s-fmt arg)))
                               ((%) "%")) out)
                    (loop (match 'after)
                          (if consumes-an-arg? (cdr args) args))
                    )))
            (ret))))))

(define (printf fmt . args) (display (apply sprintf (cons fmt args))))

(provide "nt/printf")
;;EOF

これ。できたらお題じゃなくて、トピックに回してもらえませんかねぇ…(^_^;)。←泣きそう

あと、以前の B+-Tree、一つ前のクイズスタイルのお題のときにも感じたのですが、お題の投稿に際して出題者には、ご自身の答えの事前の登録も義務づける(公開は一定時間経過後なされる)ようにしてはどうかと思いました。実際、書くとなると(出題者が)想像していたよりたいへんじゃん…てなことにならないように。