48.upstream.scm 19 KB

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