48.body.scm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. ;;; Copyright (C) Kenneth A Dickey (2003). All Rights Reserved.
  2. ;;; Made an R7RS library by Taylan Ulrich Bayırlı/Kammer, Copyright (C) 2014.
  3. ;;; Permission is hereby granted, free of charge, to any person obtaining a copy
  4. ;;; of this software and associated documentation files (the "Software"), to
  5. ;;; deal in the Software without restriction, including without limitation the
  6. ;;; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  7. ;;; sell copies of the Software, and to permit persons to whom the Software is
  8. ;;; furnished to do so, subject to the following conditions:
  9. ;;; The above copyright notice and this permission notice shall be included in
  10. ;;; all copies or substantial portions of the Software.
  11. ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. ;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. ;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. ;;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. ;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  16. ;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  17. ;;; IN THE SOFTWARE.
  18. (define ascii-tab #\tab) ;; NB: assumes ASCII encoding
  19. (define dont-print (if #f #f))
  20. (define pretty-print write) ; ugly but permitted
  21. (define (format . args)
  22. (cond
  23. ((null? args)
  24. (error "FORMAT: required format-string argument is missing")
  25. )
  26. ((string? (car args))
  27. (apply format (cons #f args)))
  28. ((< (length args) 2)
  29. (error (format #f "FORMAT: too few arguments ~s" (cons 'format args)))
  30. )
  31. (else
  32. (let ( (output-port (car args))
  33. (format-string (cadr args))
  34. (args (cddr args))
  35. )
  36. (letrec ( (port
  37. (cond ((output-port? output-port) output-port)
  38. ((eq? output-port #t) (current-output-port))
  39. ((eq? output-port #f) (open-output-string))
  40. (else (error
  41. (format #f "FORMAT: bad output-port argument: ~s"
  42. output-port)))
  43. ) )
  44. (return-value
  45. (if (eq? output-port #f) ;; if format into a string
  46. (lambda () (get-output-string port)) ;; then return the string
  47. (lambda () dont-print)) ;; else do something harmless
  48. )
  49. )
  50. (define (string-index str c)
  51. (let ( (len (string-length str)) )
  52. (let loop ( (i 0) )
  53. (cond ((= i len) #f)
  54. ((eqv? c (string-ref str i)) i)
  55. (else (loop (+ i 1)))))))
  56. (define (string-grow str len char)
  57. (let ( (off (- len (string-length str))) )
  58. (if (positive? off)
  59. (string-append (make-string off char) str)
  60. str)))
  61. (define (compose-with-digits digits pre-str frac-str exp-str)
  62. (let ( (frac-len (string-length frac-str)) )
  63. (cond
  64. ((< frac-len digits) ;; grow frac part, pad with zeros
  65. (string-append pre-str "."
  66. frac-str (make-string (- digits frac-len) #\0)
  67. exp-str)
  68. )
  69. ((= frac-len digits) ;; frac-part is exactly the right size
  70. (string-append pre-str "."
  71. frac-str
  72. exp-str)
  73. )
  74. (else ;; must round to shrink it
  75. (let* ( (first-part (substring frac-str 0 digits))
  76. (last-part (substring frac-str digits frac-len))
  77. (temp-str
  78. (number->string
  79. (round (string->number
  80. (string-append first-part "." last-part)))))
  81. (dot-pos (string-index temp-str #\.))
  82. (carry?
  83. (and (> dot-pos digits)
  84. (> (round (string->number
  85. (string-append "0." frac-str)))
  86. 0)))
  87. (new-frac
  88. (substring temp-str 0 digits))
  89. )
  90. (string-append
  91. (if carry? (number->string (+ 1 (string->number pre-str))) pre-str)
  92. "."
  93. new-frac
  94. exp-str)))
  95. ) ) )
  96. (define (format-fixed number-or-string width digits) ; returns a string
  97. (cond
  98. ((string? number-or-string)
  99. (string-grow number-or-string width #\space)
  100. )
  101. ((number? number-or-string)
  102. (let ( (real (real-part number-or-string))
  103. (imag (imag-part number-or-string))
  104. )
  105. (cond
  106. ((not (zero? imag))
  107. (string-grow
  108. (string-append (format-fixed real 0 digits)
  109. (if (negative? imag) "" "+")
  110. (format-fixed imag 0 digits)
  111. "i")
  112. width
  113. #\space)
  114. )
  115. (digits
  116. (let* ( (num-str (number->string (inexact real)))
  117. (dot-index (string-index num-str #\.))
  118. (exp-index (string-index num-str #\e))
  119. (length (string-length num-str))
  120. (pre-string
  121. (cond
  122. (exp-index
  123. (if dot-index
  124. (substring num-str 0 dot-index)
  125. (substring num-str 0 (+ exp-index 1)))
  126. )
  127. (dot-index
  128. (substring num-str 0 dot-index)
  129. )
  130. (else
  131. num-str))
  132. )
  133. (exp-string
  134. (if exp-index (substring num-str exp-index length) "")
  135. )
  136. (frac-string
  137. (if exp-index
  138. (substring num-str (+ dot-index 1) exp-index)
  139. (substring num-str (+ dot-index 1) length))
  140. )
  141. )
  142. (string-grow
  143. (if dot-index
  144. (compose-with-digits digits
  145. pre-string
  146. frac-string
  147. exp-string)
  148. (string-append pre-string exp-string))
  149. width
  150. #\space)
  151. ))
  152. (else ;; no digits
  153. (string-grow (number->string real) width #\space)))
  154. ))
  155. (else
  156. (error
  157. (format "FORMAT: ~F requires a number or a string, got ~s" number-or-string)))
  158. ))
  159. (define documentation-string
  160. "(format [<port>] <format-string> [<arg>...]) -- <port> is #t, #f or an output-port
  161. OPTION [MNEMONIC] DESCRIPTION -- Implementation Assumes ASCII Text Encoding
  162. ~H [Help] output this text
  163. ~A [Any] (display arg) for humans
  164. ~S [Slashified] (write arg) for parsers
  165. ~W [WriteCircular] like ~s but outputs circular and recursive data structures
  166. ~~ [tilde] output a tilde
  167. ~T [Tab] output a tab character
  168. ~% [Newline] output a newline character
  169. ~& [Freshline] output a newline character if the previous output was not a newline
  170. ~D [Decimal] the arg is a number which is output in decimal radix
  171. ~X [heXadecimal] the arg is a number which is output in hexdecimal radix
  172. ~O [Octal] the arg is a number which is output in octal radix
  173. ~B [Binary] the arg is a number which is output in binary radix
  174. ~w,dF [Fixed] the arg is a string or number which has width w and d digits after the decimal
  175. ~C [Character] charater arg is output by write-char
  176. ~_ [Space] a single space character is output
  177. ~Y [Yuppify] the list arg is pretty-printed to the output
  178. ~? [Indirection] recursive format: next 2 args are format-string and list of arguments
  179. ~K [Indirection] same as ~?
  180. "
  181. )
  182. (define (require-an-arg args)
  183. (if (null? args)
  184. (error "FORMAT: too few arguments" ))
  185. )
  186. (define (format-help format-strg arglist)
  187. (letrec (
  188. (length-of-format-string (string-length format-strg))
  189. (anychar-dispatch
  190. (lambda (pos arglist last-was-newline)
  191. (if (>= pos length-of-format-string)
  192. arglist ; return unused args
  193. (let ( (char (string-ref format-strg pos)) )
  194. (cond
  195. ((eqv? char #\~)
  196. (tilde-dispatch (+ pos 1) arglist last-was-newline))
  197. (else
  198. (write-char char port)
  199. (anychar-dispatch (+ pos 1) arglist #f)
  200. ))
  201. ))
  202. )) ; end anychar-dispatch
  203. (has-newline?
  204. (lambda (whatever last-was-newline)
  205. (or (eqv? whatever #\newline)
  206. (and (string? whatever)
  207. (let ( (len (string-length whatever)) )
  208. (if (zero? len)
  209. last-was-newline
  210. (eqv? #\newline (string-ref whatever (- len 1)))))))
  211. )) ; end has-newline?
  212. (tilde-dispatch
  213. (lambda (pos arglist last-was-newline)
  214. (cond
  215. ((>= pos length-of-format-string)
  216. (write-char #\~ port) ; tilde at end of string is just output
  217. arglist ; return unused args
  218. )
  219. (else
  220. (case (char-upcase (string-ref format-strg pos))
  221. ((#\A) ; Any -- for humans
  222. (require-an-arg arglist)
  223. (let ( (whatever (car arglist)) )
  224. (display whatever port)
  225. (anychar-dispatch (+ pos 1)
  226. (cdr arglist)
  227. (has-newline? whatever last-was-newline))
  228. ))
  229. ((#\S) ; Slashified -- for parsers
  230. (require-an-arg arglist)
  231. (let ( (whatever (car arglist)) )
  232. (write whatever port)
  233. (anychar-dispatch (+ pos 1)
  234. (cdr arglist)
  235. (has-newline? whatever last-was-newline))
  236. ))
  237. ((#\W)
  238. (require-an-arg arglist)
  239. (let ( (whatever (car arglist)) )
  240. (write-shared whatever port)
  241. (anychar-dispatch (+ pos 1)
  242. (cdr arglist)
  243. (has-newline? whatever last-was-newline))
  244. ))
  245. ((#\D) ; Decimal
  246. (require-an-arg arglist)
  247. (display (number->string (car arglist) 10) port)
  248. (anychar-dispatch (+ pos 1) (cdr arglist) #f)
  249. )
  250. ((#\X) ; HeXadecimal
  251. (require-an-arg arglist)
  252. (display (number->string (car arglist) 16) port)
  253. (anychar-dispatch (+ pos 1) (cdr arglist) #f)
  254. )
  255. ((#\O) ; Octal
  256. (require-an-arg arglist)
  257. (display (number->string (car arglist) 8) port)
  258. (anychar-dispatch (+ pos 1) (cdr arglist) #f)
  259. )
  260. ((#\B) ; Binary
  261. (require-an-arg arglist)
  262. (display (number->string (car arglist) 2) port)
  263. (anychar-dispatch (+ pos 1) (cdr arglist) #f)
  264. )
  265. ((#\C) ; Character
  266. (require-an-arg arglist)
  267. (write-char (car arglist) port)
  268. (anychar-dispatch (+ pos 1) (cdr arglist) (eqv? (car arglist) #\newline))
  269. )
  270. ((#\~) ; Tilde
  271. (write-char #\~ port)
  272. (anychar-dispatch (+ pos 1) arglist #f)
  273. )
  274. ((#\%) ; Newline
  275. (newline port)
  276. (anychar-dispatch (+ pos 1) arglist #t)
  277. )
  278. ((#\&) ; Freshline
  279. (if (not last-was-newline) ;; (unless last-was-newline ..
  280. (newline port))
  281. (anychar-dispatch (+ pos 1) arglist #t)
  282. )
  283. ((#\_) ; Space
  284. (write-char #\space port)
  285. (anychar-dispatch (+ pos 1) arglist #f)
  286. )
  287. ((#\T) ; Tab -- IMPLEMENTATION DEPENDENT ENCODING
  288. (write-char ascii-tab port)
  289. (anychar-dispatch (+ pos 1) arglist #f)
  290. )
  291. ((#\Y) ; Pretty-print
  292. (pretty-print (car arglist) port) ;; IMPLEMENTATION DEPENDENT
  293. (anychar-dispatch (+ pos 1) (cdr arglist) #f)
  294. )
  295. ((#\F)
  296. (require-an-arg arglist)
  297. (display (format-fixed (car arglist) 0 #f) port)
  298. (anychar-dispatch (+ pos 1) (cdr arglist) #f)
  299. )
  300. ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9) ;; gather "~w[,d]F" w and d digits
  301. (let loop ( (index (+ pos 1))
  302. (w-digits (list (string-ref format-strg pos)))
  303. (d-digits '())
  304. (in-width? #t)
  305. )
  306. (if (>= index length-of-format-string)
  307. (error
  308. (format "FORMAT: improper numeric format directive in ~s" format-strg))
  309. (let ( (next-char (string-ref format-strg index)) )
  310. (cond
  311. ((char-numeric? next-char)
  312. (if in-width?
  313. (loop (+ index 1)
  314. (cons next-char w-digits)
  315. d-digits
  316. in-width?)
  317. (loop (+ index 1)
  318. w-digits
  319. (cons next-char d-digits)
  320. in-width?))
  321. )
  322. ((char=? next-char #\F)
  323. (let ( (width (string->number (list->string (reverse w-digits))))
  324. (digits (if (zero? (length d-digits))
  325. #f
  326. (string->number (list->string (reverse d-digits)))))
  327. )
  328. (display (format-fixed (car arglist) width digits) port)
  329. (anychar-dispatch (+ index 1) (cdr arglist) #f))
  330. )
  331. ((char=? next-char #\,)
  332. (if in-width?
  333. (loop (+ index 1)
  334. w-digits
  335. d-digits
  336. #f)
  337. (error
  338. (format "FORMAT: too many commas in directive ~s" format-strg)))
  339. )
  340. (else
  341. (error (format "FORMAT: ~~w.dF directive ill-formed in ~s" format-strg))))))
  342. ))
  343. ((#\? #\K) ; indirection -- take next arg as format string
  344. (cond ; and following arg as list of format args
  345. ((< (length arglist) 2)
  346. (error
  347. (format "FORMAT: less arguments than specified for ~~?: ~s" arglist))
  348. )
  349. ((not (string? (car arglist)))
  350. (error
  351. (format "FORMAT: ~~? requires a string: ~s" (car arglist)))
  352. )
  353. (else
  354. (format-help (car arglist) (cadr arglist))
  355. (anychar-dispatch (+ pos 1) (cddr arglist) #f)
  356. )))
  357. ((#\H) ; Help
  358. (display documentation-string port)
  359. (anychar-dispatch (+ pos 1) arglist #t)
  360. )
  361. (else
  362. (error (format "FORMAT: unknown tilde escape: ~s"
  363. (string-ref format-strg pos))))
  364. )))
  365. )) ; end tilde-dispatch
  366. ) ; end letrec
  367. ; format-help main
  368. (anychar-dispatch 0 arglist #f)
  369. )) ; end format-help
  370. ; format main
  371. (let ( (unused-args (format-help format-string args)) )
  372. (if (not (null? unused-args))
  373. (error
  374. (format "FORMAT: unused arguments ~s" unused-args)))
  375. (return-value))
  376. )) ; end letrec, if
  377. ))) ; end format