Commit | Line | Data |
---|---|---|
7da43e41 JB |
1 | ;;;; "format.scm" Common LISP text output formatter for SLIB |
2 | ;;; Written 1992-1994 by Dirk Lutzebaeck (lutzeb@cs.tu-berlin.de) | |
3 | ;;; Assimilated into Guile May 1999 | |
4 | ; | |
5 | ; This code is in the public domain. | |
6 | ||
7 | ; Authors of the original version (< 1.4) were Ken Dickey and Aubrey Jaffer. | |
8 | ; Please send error reports to bug-guile@gnu.org. | |
9 | ; For documentation see slib.texi and format.doc. | |
10 | ; For testing load formatst.scm. | |
11 | ; | |
12 | ; Version 3.0 | |
13 | ||
b337528f | 14 | (define-module (ice-9 format) |
3cc34e16 | 15 | :use-module (ice-9 and-let-star) |
b337528f MV |
16 | :autoload (ice-9 pretty-print) (pretty-print)) |
17 | ||
7da43e41 JB |
18 | (export format |
19 | format:symbol-case-conv | |
20 | format:iobj-case-conv | |
21 | format:expch) | |
22 | ||
23 | ;;; Configuration ------------------------------------------------------------ | |
24 | ||
25 | (define format:symbol-case-conv #f) | |
26 | ;; Symbols are converted by symbol->string so the case of the printed | |
27 | ;; symbols is implementation dependent. format:symbol-case-conv is a | |
28 | ;; one arg closure which is either #f (no conversion), string-upcase!, | |
29 | ;; string-downcase! or string-capitalize!. | |
30 | ||
31 | (define format:iobj-case-conv #f) | |
32 | ;; As format:symbol-case-conv but applies for the representation of | |
33 | ;; implementation internal objects. | |
34 | ||
35 | (define format:expch #\E) | |
36 | ;; The character prefixing the exponent value in ~e printing. | |
37 | ||
38 | (define format:floats (provided? 'inexact)) | |
39 | ;; Detects if the scheme system implements flonums (see at eof). | |
40 | ||
41 | (define format:complex-numbers (provided? 'complex)) | |
42 | ;; Detects if the scheme system implements complex numbers. | |
43 | ||
44 | (define format:radix-pref (char=? #\# (string-ref (number->string 8 8) 0))) | |
45 | ;; Detects if number->string adds a radix prefix. | |
46 | ||
47 | (define format:ascii-non-printable-charnames | |
48 | '#("nul" "soh" "stx" "etx" "eot" "enq" "ack" "bel" | |
49 | "bs" "ht" "nl" "vt" "np" "cr" "so" "si" | |
50 | "dle" "dc1" "dc2" "dc3" "dc4" "nak" "syn" "etb" | |
51 | "can" "em" "sub" "esc" "fs" "gs" "rs" "us" "space")) | |
52 | ||
53 | ;;; End of configuration ---------------------------------------------------- | |
54 | ||
55 | (define format:version "3.0") | |
56 | (define format:port #f) ; curr. format output port | |
57 | (define format:output-col 0) ; curr. format output tty column | |
58 | (define format:flush-output #f) ; flush output at end of formatting | |
59 | (define format:case-conversion #f) | |
60 | (define format:error-continuation #f) | |
61 | (define format:args #f) | |
62 | (define format:pos 0) ; curr. format string parsing position | |
63 | (define format:arg-pos 0) ; curr. format argument position | |
64 | ; this is global for error presentation | |
65 | ||
66 | ; format string and char output routines on format:port | |
67 | ||
68 | (define (format:out-str str) | |
69 | (if format:case-conversion | |
70 | (display (format:case-conversion str) format:port) | |
71 | (display str format:port)) | |
72 | (set! format:output-col | |
73 | (+ format:output-col (string-length str)))) | |
74 | ||
75 | (define (format:out-char ch) | |
76 | (if format:case-conversion | |
77 | (display (format:case-conversion (string ch)) format:port) | |
78 | (write-char ch format:port)) | |
79 | (set! format:output-col | |
80 | (if (char=? ch #\newline) | |
81 | 0 | |
82 | (+ format:output-col 1)))) | |
83 | ||
84 | ;(define (format:out-substr str i n) ; this allocates a new string | |
85 | ; (display (substring str i n) format:port) | |
86 | ; (set! format:output-col (+ format:output-col n))) | |
87 | ||
88 | (define (format:out-substr str i n) | |
89 | (do ((k i (+ k 1))) | |
90 | ((= k n)) | |
91 | (write-char (string-ref str k) format:port)) | |
92 | (set! format:output-col (+ format:output-col n))) | |
93 | ||
94 | ;(define (format:out-fill n ch) ; this allocates a new string | |
95 | ; (format:out-str (make-string n ch))) | |
96 | ||
97 | (define (format:out-fill n ch) | |
98 | (do ((i 0 (+ i 1))) | |
99 | ((= i n)) | |
100 | (write-char ch format:port)) | |
101 | (set! format:output-col (+ format:output-col n))) | |
102 | ||
103 | ; format's user error handler | |
104 | ||
105 | (define (format:error . args) ; never returns! | |
106 | (let ((error-continuation format:error-continuation) | |
107 | (format-args format:args) | |
108 | (port (current-error-port))) | |
109 | (set! format:error format:intern-error) | |
110 | (if (and (>= (length format:args) 2) | |
111 | (string? (cadr format:args))) | |
112 | (let ((format-string (cadr format-args))) | |
113 | (if (not (zero? format:arg-pos)) | |
114 | (set! format:arg-pos (- format:arg-pos 1))) | |
115 | (format port "~%FORMAT: error with call: (format ~a \"~a<===~a\" ~ | |
116 | ~{~a ~}===>~{~a ~})~% " | |
117 | (car format:args) | |
118 | (substring format-string 0 format:pos) | |
119 | (substring format-string format:pos | |
120 | (string-length format-string)) | |
121 | (list-head (cddr format:args) format:arg-pos) | |
122 | (list-tail (cddr format:args) format:arg-pos))) | |
123 | (format port | |
124 | "~%FORMAT: error with call: (format~{ ~a~})~% " | |
125 | format:args)) | |
126 | (apply format port args) | |
127 | (newline port) | |
128 | (set! format:error format:error-save) | |
129 | (set! format:error-continuation error-continuation) | |
130 | (format:abort) | |
131 | (format:intern-error "format:abort does not jump to toplevel!"))) | |
132 | ||
133 | (define format:error-save format:error) | |
134 | ||
135 | (define (format:intern-error . args) ;if something goes wrong in format:error | |
136 | (display "FORMAT: INTERNAL ERROR IN FORMAT:ERROR!") (newline) | |
137 | (display " format args: ") (write format:args) (newline) | |
138 | (display " error args: ") (write args) (newline) | |
139 | (set! format:error format:error-save) | |
140 | (format:abort)) | |
141 | ||
142 | (define (format:format . args) ; the formatter entry | |
143 | (set! format:args args) | |
144 | (set! format:arg-pos 0) | |
145 | (set! format:pos 0) | |
146 | (if (< (length args) 1) | |
147 | (format:error "not enough arguments")) | |
148 | ||
149 | ;; If the first argument is a string, then that's the format string. | |
150 | ;; (Scheme->C) | |
151 | ;; In this case, put the argument list in canonical form. | |
152 | (let ((args (if (string? (car args)) | |
153 | (cons #f args) | |
154 | args))) | |
155 | ;; Use this canonicalized version when reporting errors. | |
156 | (set! format:args args) | |
157 | ||
158 | (let ((destination (car args)) | |
159 | (arglist (cdr args))) | |
160 | (cond | |
161 | ((or (and (boolean? destination) ; port output | |
162 | destination) | |
163 | (output-port? destination) | |
164 | (number? destination)) | |
165 | (format:out (cond | |
166 | ((boolean? destination) (current-output-port)) | |
167 | ((output-port? destination) destination) | |
168 | ((number? destination) (current-error-port))) | |
169 | (car arglist) (cdr arglist))) | |
170 | ((and (boolean? destination) ; string output | |
171 | (not destination)) | |
172 | (call-with-output-string | |
173 | (lambda (port) (format:out port (car arglist) (cdr arglist))))) | |
174 | (else | |
175 | (format:error "illegal destination `~a'" destination)))))) | |
176 | ||
177 | (define (format:out port fmt args) ; the output handler for a port | |
178 | (set! format:port port) ; global port for output routines | |
179 | (set! format:case-conversion #f) ; modifier case conversion procedure | |
180 | (set! format:flush-output #f) ; ~! reset | |
3cc34e16 MV |
181 | (and-let* ((col (port-column port))) ; get current column from port |
182 | (set! format:output-col col)) | |
7da43e41 JB |
183 | (let ((arg-pos (format:format-work fmt args)) |
184 | (arg-len (length args))) | |
185 | (cond | |
186 | ((< arg-pos arg-len) | |
187 | (set! format:arg-pos (+ arg-pos 1)) | |
188 | (set! format:pos (string-length fmt)) | |
189 | (format:error "~a superfluous argument~:p" (- arg-len arg-pos))) | |
190 | ((> arg-pos arg-len) | |
191 | (set! format:arg-pos (+ arg-len 1)) | |
192 | (display format:arg-pos) | |
193 | (format:error "~a missing argument~:p" (- arg-pos arg-len))) | |
194 | (else | |
195 | (if format:flush-output (force-output port)) | |
196 | #t)))) | |
197 | ||
198 | (define format:parameter-characters | |
199 | '(#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\- #\+ #\v #\# #\')) | |
200 | ||
201 | (define (format:format-work format-string arglist) ; does the formatting work | |
202 | (letrec | |
203 | ((format-string-len (string-length format-string)) | |
204 | (arg-pos 0) ; argument position in arglist | |
205 | (arg-len (length arglist)) ; number of arguments | |
206 | (modifier #f) ; 'colon | 'at | 'colon-at | #f | |
207 | (params '()) ; directive parameter list | |
208 | (param-value-found #f) ; a directive parameter value found | |
209 | (conditional-nest 0) ; conditional nesting level | |
210 | (clause-pos 0) ; last cond. clause beginning char pos | |
211 | (clause-default #f) ; conditional default clause string | |
212 | (clauses '()) ; conditional clause string list | |
213 | (conditional-type #f) ; reflects the contional modifiers | |
214 | (conditional-arg #f) ; argument to apply the conditional | |
215 | (iteration-nest 0) ; iteration nesting level | |
216 | (iteration-pos 0) ; iteration string beginning char pos | |
217 | (iteration-type #f) ; reflects the iteration modifiers | |
218 | (max-iterations #f) ; maximum number of iterations | |
219 | (recursive-pos-save format:pos) | |
220 | ||
221 | (next-char ; gets the next char from format-string | |
222 | (lambda () | |
223 | (let ((ch (peek-next-char))) | |
224 | (set! format:pos (+ 1 format:pos)) | |
225 | ch))) | |
226 | ||
227 | (peek-next-char | |
228 | (lambda () | |
229 | (if (>= format:pos format-string-len) | |
230 | (format:error "illegal format string") | |
231 | (string-ref format-string format:pos)))) | |
232 | ||
233 | (one-positive-integer? | |
234 | (lambda (params) | |
235 | (cond | |
236 | ((null? params) #f) | |
237 | ((and (integer? (car params)) | |
238 | (>= (car params) 0) | |
239 | (= (length params) 1)) #t) | |
240 | (else (format:error "one positive integer parameter expected"))))) | |
241 | ||
242 | (next-arg | |
243 | (lambda () | |
244 | (if (>= arg-pos arg-len) | |
245 | (begin | |
246 | (set! format:arg-pos (+ arg-len 1)) | |
247 | (format:error "missing argument(s)"))) | |
248 | (add-arg-pos 1) | |
249 | (list-ref arglist (- arg-pos 1)))) | |
250 | ||
251 | (prev-arg | |
252 | (lambda () | |
253 | (add-arg-pos -1) | |
254 | (if (negative? arg-pos) | |
255 | (format:error "missing backward argument(s)")) | |
256 | (list-ref arglist arg-pos))) | |
257 | ||
258 | (rest-args | |
259 | (lambda () | |
260 | (let loop ((l arglist) (k arg-pos)) ; list-tail definition | |
261 | (if (= k 0) l (loop (cdr l) (- k 1)))))) | |
262 | ||
263 | (add-arg-pos | |
264 | (lambda (n) | |
265 | (set! arg-pos (+ n arg-pos)) | |
266 | (set! format:arg-pos arg-pos))) | |
267 | ||
268 | (anychar-dispatch ; dispatches the format-string | |
269 | (lambda () | |
270 | (if (>= format:pos format-string-len) | |
271 | arg-pos ; used for ~? continuance | |
272 | (let ((char (next-char))) | |
273 | (cond | |
274 | ((char=? char #\~) | |
275 | (set! modifier #f) | |
276 | (set! params '()) | |
277 | (set! param-value-found #f) | |
278 | (tilde-dispatch)) | |
279 | (else | |
280 | (if (and (zero? conditional-nest) | |
281 | (zero? iteration-nest)) | |
282 | (format:out-char char)) | |
283 | (anychar-dispatch))))))) | |
284 | ||
285 | (tilde-dispatch | |
286 | (lambda () | |
287 | (cond | |
288 | ((>= format:pos format-string-len) | |
289 | (format:out-str "~") ; tilde at end of string is just output | |
290 | arg-pos) ; used for ~? continuance | |
291 | ((and (or (zero? conditional-nest) | |
292 | (memv (peek-next-char) ; find conditional directives | |
293 | (append '(#\[ #\] #\; #\: #\@ #\^) | |
294 | format:parameter-characters))) | |
295 | (or (zero? iteration-nest) | |
296 | (memv (peek-next-char) ; find iteration directives | |
297 | (append '(#\{ #\} #\: #\@ #\^) | |
298 | format:parameter-characters)))) | |
299 | (case (char-upcase (next-char)) | |
300 | ||
301 | ;; format directives | |
302 | ||
303 | ((#\A) ; Any -- for humans | |
304 | (set! format:read-proof (memq modifier '(colon colon-at))) | |
305 | (format:out-obj-padded (memq modifier '(at colon-at)) | |
306 | (next-arg) #f params) | |
307 | (anychar-dispatch)) | |
308 | ((#\S) ; Slashified -- for parsers | |
309 | (set! format:read-proof (memq modifier '(colon colon-at))) | |
310 | (format:out-obj-padded (memq modifier '(at colon-at)) | |
311 | (next-arg) #t params) | |
312 | (anychar-dispatch)) | |
313 | ((#\D) ; Decimal | |
314 | (format:out-num-padded modifier (next-arg) params 10) | |
315 | (anychar-dispatch)) | |
316 | ((#\X) ; Hexadecimal | |
317 | (format:out-num-padded modifier (next-arg) params 16) | |
318 | (anychar-dispatch)) | |
319 | ((#\O) ; Octal | |
320 | (format:out-num-padded modifier (next-arg) params 8) | |
321 | (anychar-dispatch)) | |
322 | ((#\B) ; Binary | |
323 | (format:out-num-padded modifier (next-arg) params 2) | |
324 | (anychar-dispatch)) | |
325 | ((#\R) | |
326 | (if (null? params) | |
327 | (format:out-obj-padded ; Roman, cardinal, ordinal numerals | |
328 | #f | |
329 | ((case modifier | |
330 | ((at) format:num->roman) | |
331 | ((colon-at) format:num->old-roman) | |
332 | ((colon) format:num->ordinal) | |
333 | (else format:num->cardinal)) | |
334 | (next-arg)) | |
335 | #f params) | |
336 | (format:out-num-padded ; any Radix | |
337 | modifier (next-arg) (cdr params) (car params))) | |
338 | (anychar-dispatch)) | |
339 | ((#\F) ; Fixed-format floating-point | |
340 | (if format:floats | |
341 | (format:out-fixed modifier (next-arg) params) | |
342 | (format:out-str (number->string (next-arg)))) | |
343 | (anychar-dispatch)) | |
344 | ((#\E) ; Exponential floating-point | |
345 | (if format:floats | |
346 | (format:out-expon modifier (next-arg) params) | |
347 | (format:out-str (number->string (next-arg)))) | |
348 | (anychar-dispatch)) | |
349 | ((#\G) ; General floating-point | |
350 | (if format:floats | |
351 | (format:out-general modifier (next-arg) params) | |
352 | (format:out-str (number->string (next-arg)))) | |
353 | (anychar-dispatch)) | |
354 | ((#\$) ; Dollars floating-point | |
355 | (if format:floats | |
356 | (format:out-dollar modifier (next-arg) params) | |
357 | (format:out-str (number->string (next-arg)))) | |
358 | (anychar-dispatch)) | |
359 | ((#\I) ; Complex numbers | |
360 | (if (not format:complex-numbers) | |
361 | (format:error | |
362 | "complex numbers not supported by this scheme system")) | |
363 | (let ((z (next-arg))) | |
364 | (if (not (complex? z)) | |
365 | (format:error "argument not a complex number")) | |
366 | (format:out-fixed modifier (real-part z) params) | |
367 | (format:out-fixed 'at (imag-part z) params) | |
368 | (format:out-char #\i)) | |
369 | (anychar-dispatch)) | |
370 | ((#\C) ; Character | |
371 | (let ((ch (if (one-positive-integer? params) | |
372 | (integer->char (car params)) | |
373 | (next-arg)))) | |
374 | (if (not (char? ch)) (format:error "~~c expects a character")) | |
375 | (case modifier | |
376 | ((at) | |
377 | (format:out-str (format:char->str ch))) | |
378 | ((colon) | |
379 | (let ((c (char->integer ch))) | |
380 | (if (< c 0) | |
381 | (set! c (+ c 256))) ; compensate complement impl. | |
382 | (cond | |
383 | ((< c #x20) ; assumes that control chars are < #x20 | |
384 | (format:out-char #\^) | |
385 | (format:out-char | |
386 | (integer->char (+ c #x40)))) | |
387 | ((>= c #x7f) | |
388 | (format:out-str "#\\") | |
389 | (format:out-str | |
390 | (if format:radix-pref | |
391 | (let ((s (number->string c 8))) | |
392 | (substring s 2 (string-length s))) | |
393 | (number->string c 8)))) | |
394 | (else | |
395 | (format:out-char ch))))) | |
396 | (else (format:out-char ch)))) | |
397 | (anychar-dispatch)) | |
398 | ((#\P) ; Plural | |
399 | (if (memq modifier '(colon colon-at)) | |
400 | (prev-arg)) | |
401 | (let ((arg (next-arg))) | |
402 | (if (not (number? arg)) | |
403 | (format:error "~~p expects a number argument")) | |
404 | (if (= arg 1) | |
405 | (if (memq modifier '(at colon-at)) | |
406 | (format:out-char #\y)) | |
407 | (if (memq modifier '(at colon-at)) | |
408 | (format:out-str "ies") | |
409 | (format:out-char #\s)))) | |
410 | (anychar-dispatch)) | |
411 | ((#\~) ; Tilde | |
412 | (if (one-positive-integer? params) | |
413 | (format:out-fill (car params) #\~) | |
414 | (format:out-char #\~)) | |
415 | (anychar-dispatch)) | |
416 | ((#\%) ; Newline | |
417 | (if (one-positive-integer? params) | |
418 | (format:out-fill (car params) #\newline) | |
419 | (format:out-char #\newline)) | |
420 | (set! format:output-col 0) | |
421 | (anychar-dispatch)) | |
422 | ((#\&) ; Fresh line | |
423 | (if (one-positive-integer? params) | |
424 | (begin | |
425 | (if (> (car params) 0) | |
426 | (format:out-fill (- (car params) | |
427 | (if (> format:output-col 0) 0 1)) | |
428 | #\newline)) | |
429 | (set! format:output-col 0)) | |
430 | (if (> format:output-col 0) | |
431 | (format:out-char #\newline))) | |
432 | (anychar-dispatch)) | |
433 | ((#\_) ; Space character | |
434 | (if (one-positive-integer? params) | |
435 | (format:out-fill (car params) #\space) | |
436 | (format:out-char #\space)) | |
437 | (anychar-dispatch)) | |
438 | ((#\/) ; Tabulator character | |
439 | (if (one-positive-integer? params) | |
8a7391cd JB |
440 | (format:out-fill (car params) #\tab) |
441 | (format:out-char #\tab)) | |
7da43e41 JB |
442 | (anychar-dispatch)) |
443 | ((#\|) ; Page seperator | |
444 | (if (one-positive-integer? params) | |
8a7391cd JB |
445 | (format:out-fill (car params) #\page) |
446 | (format:out-char #\page)) | |
7da43e41 JB |
447 | (set! format:output-col 0) |
448 | (anychar-dispatch)) | |
449 | ((#\T) ; Tabulate | |
450 | (format:tabulate modifier params) | |
451 | (anychar-dispatch)) | |
452 | ((#\Y) ; Pretty-print | |
7da43e41 JB |
453 | (pretty-print (next-arg) format:port) |
454 | (set! format:output-col 0) | |
455 | (anychar-dispatch)) | |
456 | ((#\? #\K) ; Indirection (is "~K" in T-Scheme) | |
457 | (cond | |
458 | ((memq modifier '(colon colon-at)) | |
459 | (format:error "illegal modifier in ~~?")) | |
460 | ((eq? modifier 'at) | |
461 | (let* ((frmt (next-arg)) | |
462 | (args (rest-args))) | |
463 | (add-arg-pos (format:format-work frmt args)))) | |
464 | (else | |
465 | (let* ((frmt (next-arg)) | |
466 | (args (next-arg))) | |
467 | (format:format-work frmt args)))) | |
468 | (anychar-dispatch)) | |
469 | ((#\!) ; Flush output | |
470 | (set! format:flush-output #t) | |
471 | (anychar-dispatch)) | |
472 | ((#\newline) ; Continuation lines | |
473 | (if (eq? modifier 'at) | |
474 | (format:out-char #\newline)) | |
475 | (if (< format:pos format-string-len) | |
476 | (do ((ch (peek-next-char) (peek-next-char))) | |
477 | ((or (not (char-whitespace? ch)) | |
478 | (= format:pos (- format-string-len 1)))) | |
479 | (if (eq? modifier 'colon) | |
480 | (format:out-char (next-char)) | |
481 | (next-char)))) | |
482 | (anychar-dispatch)) | |
483 | ((#\*) ; Argument jumping | |
484 | (case modifier | |
485 | ((colon) ; jump backwards | |
486 | (if (one-positive-integer? params) | |
487 | (do ((i 0 (+ i 1))) | |
488 | ((= i (car params))) | |
489 | (prev-arg)) | |
490 | (prev-arg))) | |
491 | ((at) ; jump absolute | |
492 | (set! arg-pos (if (one-positive-integer? params) | |
493 | (car params) 0))) | |
494 | ((colon-at) | |
495 | (format:error "illegal modifier `:@' in ~~* directive")) | |
496 | (else ; jump forward | |
497 | (if (one-positive-integer? params) | |
498 | (do ((i 0 (+ i 1))) | |
499 | ((= i (car params))) | |
500 | (next-arg)) | |
501 | (next-arg)))) | |
502 | (anychar-dispatch)) | |
503 | ((#\() ; Case conversion begin | |
504 | (set! format:case-conversion | |
505 | (case modifier | |
506 | ((at) string-capitalize-first) | |
507 | ((colon) string-capitalize) | |
508 | ((colon-at) string-upcase) | |
509 | (else string-downcase))) | |
510 | (anychar-dispatch)) | |
511 | ((#\)) ; Case conversion end | |
512 | (if (not format:case-conversion) | |
513 | (format:error "missing ~~(")) | |
514 | (set! format:case-conversion #f) | |
515 | (anychar-dispatch)) | |
516 | ((#\[) ; Conditional begin | |
517 | (set! conditional-nest (+ conditional-nest 1)) | |
518 | (cond | |
519 | ((= conditional-nest 1) | |
520 | (set! clause-pos format:pos) | |
521 | (set! clause-default #f) | |
522 | (set! clauses '()) | |
523 | (set! conditional-type | |
524 | (case modifier | |
525 | ((at) 'if-then) | |
526 | ((colon) 'if-else-then) | |
527 | ((colon-at) (format:error "illegal modifier in ~~[")) | |
528 | (else 'num-case))) | |
529 | (set! conditional-arg | |
530 | (if (one-positive-integer? params) | |
531 | (car params) | |
532 | (next-arg))))) | |
533 | (anychar-dispatch)) | |
534 | ((#\;) ; Conditional separator | |
535 | (if (zero? conditional-nest) | |
536 | (format:error "~~; not in ~~[~~] conditional")) | |
537 | (if (not (null? params)) | |
538 | (format:error "no parameter allowed in ~~;")) | |
539 | (if (= conditional-nest 1) | |
540 | (let ((clause-str | |
541 | (cond | |
542 | ((eq? modifier 'colon) | |
543 | (set! clause-default #t) | |
544 | (substring format-string clause-pos | |
545 | (- format:pos 3))) | |
546 | ((memq modifier '(at colon-at)) | |
547 | (format:error "illegal modifier in ~~;")) | |
548 | (else | |
549 | (substring format-string clause-pos | |
550 | (- format:pos 2)))))) | |
551 | (set! clauses (append clauses (list clause-str))) | |
552 | (set! clause-pos format:pos))) | |
553 | (anychar-dispatch)) | |
554 | ((#\]) ; Conditional end | |
555 | (if (zero? conditional-nest) (format:error "missing ~~[")) | |
556 | (set! conditional-nest (- conditional-nest 1)) | |
557 | (if modifier | |
558 | (format:error "no modifier allowed in ~~]")) | |
559 | (if (not (null? params)) | |
560 | (format:error "no parameter allowed in ~~]")) | |
561 | (cond | |
562 | ((zero? conditional-nest) | |
563 | (let ((clause-str (substring format-string clause-pos | |
564 | (- format:pos 2)))) | |
565 | (if clause-default | |
566 | (set! clause-default clause-str) | |
567 | (set! clauses (append clauses (list clause-str))))) | |
568 | (case conditional-type | |
569 | ((if-then) | |
570 | (if conditional-arg | |
571 | (format:format-work (car clauses) | |
572 | (list conditional-arg)))) | |
573 | ((if-else-then) | |
574 | (add-arg-pos | |
575 | (format:format-work (if conditional-arg | |
576 | (cadr clauses) | |
577 | (car clauses)) | |
578 | (rest-args)))) | |
579 | ((num-case) | |
580 | (if (or (not (integer? conditional-arg)) | |
581 | (< conditional-arg 0)) | |
582 | (format:error "argument not a positive integer")) | |
583 | (if (not (and (>= conditional-arg (length clauses)) | |
584 | (not clause-default))) | |
585 | (add-arg-pos | |
586 | (format:format-work | |
587 | (if (>= conditional-arg (length clauses)) | |
588 | clause-default | |
589 | (list-ref clauses conditional-arg)) | |
590 | (rest-args)))))))) | |
591 | (anychar-dispatch)) | |
592 | ((#\{) ; Iteration begin | |
593 | (set! iteration-nest (+ iteration-nest 1)) | |
594 | (cond | |
595 | ((= iteration-nest 1) | |
596 | (set! iteration-pos format:pos) | |
597 | (set! iteration-type | |
598 | (case modifier | |
599 | ((at) 'rest-args) | |
600 | ((colon) 'sublists) | |
601 | ((colon-at) 'rest-sublists) | |
602 | (else 'list))) | |
603 | (set! max-iterations (if (one-positive-integer? params) | |
604 | (car params) #f)))) | |
605 | (anychar-dispatch)) | |
606 | ((#\}) ; Iteration end | |
607 | (if (zero? iteration-nest) (format:error "missing ~~{")) | |
608 | (set! iteration-nest (- iteration-nest 1)) | |
609 | (case modifier | |
610 | ((colon) | |
611 | (if (not max-iterations) (set! max-iterations 1))) | |
612 | ((colon-at at) (format:error "illegal modifier")) | |
613 | (else (if (not max-iterations) (set! max-iterations 100)))) | |
614 | (if (not (null? params)) | |
615 | (format:error "no parameters allowed in ~~}")) | |
616 | (if (zero? iteration-nest) | |
617 | (let ((iteration-str | |
618 | (substring format-string iteration-pos | |
619 | (- format:pos (if modifier 3 2))))) | |
620 | (if (string=? iteration-str "") | |
621 | (set! iteration-str (next-arg))) | |
622 | (case iteration-type | |
623 | ((list) | |
624 | (let ((args (next-arg)) | |
625 | (args-len 0)) | |
626 | (if (not (list? args)) | |
627 | (format:error "expected a list argument")) | |
628 | (set! args-len (length args)) | |
629 | (do ((arg-pos 0 (+ arg-pos | |
630 | (format:format-work | |
631 | iteration-str | |
632 | (list-tail args arg-pos)))) | |
633 | (i 0 (+ i 1))) | |
634 | ((or (>= arg-pos args-len) | |
635 | (>= i max-iterations)))))) | |
636 | ((sublists) | |
637 | (let ((args (next-arg)) | |
638 | (args-len 0)) | |
639 | (if (not (list? args)) | |
640 | (format:error "expected a list argument")) | |
641 | (set! args-len (length args)) | |
642 | (do ((arg-pos 0 (+ arg-pos 1))) | |
643 | ((or (>= arg-pos args-len) | |
644 | (>= arg-pos max-iterations))) | |
645 | (let ((sublist (list-ref args arg-pos))) | |
646 | (if (not (list? sublist)) | |
647 | (format:error | |
648 | "expected a list of lists argument")) | |
649 | (format:format-work iteration-str sublist))))) | |
650 | ((rest-args) | |
651 | (let* ((args (rest-args)) | |
652 | (args-len (length args)) | |
653 | (usedup-args | |
654 | (do ((arg-pos 0 (+ arg-pos | |
655 | (format:format-work | |
656 | iteration-str | |
657 | (list-tail | |
658 | args arg-pos)))) | |
659 | (i 0 (+ i 1))) | |
660 | ((or (>= arg-pos args-len) | |
661 | (>= i max-iterations)) | |
662 | arg-pos)))) | |
663 | (add-arg-pos usedup-args))) | |
664 | ((rest-sublists) | |
665 | (let* ((args (rest-args)) | |
666 | (args-len (length args)) | |
667 | (usedup-args | |
668 | (do ((arg-pos 0 (+ arg-pos 1))) | |
669 | ((or (>= arg-pos args-len) | |
670 | (>= arg-pos max-iterations)) | |
671 | arg-pos) | |
672 | (let ((sublist (list-ref args arg-pos))) | |
673 | (if (not (list? sublist)) | |
674 | (format:error "expected list arguments")) | |
675 | (format:format-work iteration-str sublist))))) | |
676 | (add-arg-pos usedup-args))) | |
677 | (else (format:error "internal error in ~~}"))))) | |
678 | (anychar-dispatch)) | |
679 | ((#\^) ; Up and out | |
680 | (let* ((continue | |
681 | (cond | |
682 | ((not (null? params)) | |
683 | (not | |
684 | (case (length params) | |
685 | ((1) (zero? (car params))) | |
686 | ((2) (= (list-ref params 0) (list-ref params 1))) | |
687 | ((3) (<= (list-ref params 0) | |
688 | (list-ref params 1) | |
689 | (list-ref params 2))) | |
690 | (else (format:error "too much parameters"))))) | |
691 | (format:case-conversion ; if conversion stop conversion | |
692 | (set! format:case-conversion string-copy) #t) | |
693 | ((= iteration-nest 1) #t) | |
694 | ((= conditional-nest 1) #t) | |
695 | ((>= arg-pos arg-len) | |
696 | (set! format:pos format-string-len) #f) | |
697 | (else #t)))) | |
698 | (if continue | |
699 | (anychar-dispatch)))) | |
700 | ||
701 | ;; format directive modifiers and parameters | |
702 | ||
703 | ((#\@) ; `@' modifier | |
704 | (if (memq modifier '(at colon-at)) | |
705 | (format:error "double `@' modifier")) | |
706 | (set! modifier (if (eq? modifier 'colon) 'colon-at 'at)) | |
707 | (tilde-dispatch)) | |
708 | ((#\:) ; `:' modifier | |
709 | (if (memq modifier '(colon colon-at)) | |
710 | (format:error "double `:' modifier")) | |
711 | (set! modifier (if (eq? modifier 'at) 'colon-at 'colon)) | |
712 | (tilde-dispatch)) | |
713 | ((#\') ; Character parameter | |
714 | (if modifier (format:error "misplaced modifier")) | |
715 | (set! params (append params (list (char->integer (next-char))))) | |
716 | (set! param-value-found #t) | |
717 | (tilde-dispatch)) | |
718 | ((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9 #\- #\+) ; num. paramtr | |
719 | (if modifier (format:error "misplaced modifier")) | |
720 | (let ((num-str-beg (- format:pos 1)) | |
721 | (num-str-end format:pos)) | |
722 | (do ((ch (peek-next-char) (peek-next-char))) | |
723 | ((not (char-numeric? ch))) | |
724 | (next-char) | |
725 | (set! num-str-end (+ 1 num-str-end))) | |
726 | (set! params | |
727 | (append params | |
728 | (list (string->number | |
729 | (substring format-string | |
730 | num-str-beg | |
731 | num-str-end)))))) | |
732 | (set! param-value-found #t) | |
733 | (tilde-dispatch)) | |
734 | ((#\V) ; Variable parameter from next argum. | |
735 | (if modifier (format:error "misplaced modifier")) | |
736 | (set! params (append params (list (next-arg)))) | |
737 | (set! param-value-found #t) | |
738 | (tilde-dispatch)) | |
739 | ((#\#) ; Parameter is number of remaining args | |
740 | (if modifier (format:error "misplaced modifier")) | |
741 | (set! params (append params (list (length (rest-args))))) | |
742 | (set! param-value-found #t) | |
743 | (tilde-dispatch)) | |
744 | ((#\,) ; Parameter separators | |
745 | (if modifier (format:error "misplaced modifier")) | |
746 | (if (not param-value-found) | |
747 | (set! params (append params '(#f)))) ; append empty paramtr | |
748 | (set! param-value-found #f) | |
749 | (tilde-dispatch)) | |
750 | ((#\Q) ; Inquiry messages | |
751 | (if (eq? modifier 'colon) | |
752 | (format:out-str format:version) | |
753 | (let ((nl (string #\newline))) | |
754 | (format:out-str | |
755 | (string-append | |
756 | "SLIB Common LISP format version " format:version nl | |
757 | " (C) copyright 1992-1994 by Dirk Lutzebaeck" nl | |
758 | " please send bug reports to `lutzeb@cs.tu-berlin.de'" | |
759 | nl)))) | |
760 | (anychar-dispatch)) | |
761 | (else ; Unknown tilde directive | |
762 | (format:error "unknown control character `~c'" | |
763 | (string-ref format-string (- format:pos 1)))))) | |
764 | (else (anychar-dispatch)))))) ; in case of conditional | |
765 | ||
766 | (set! format:pos 0) | |
767 | (set! format:arg-pos 0) | |
768 | (anychar-dispatch) ; start the formatting | |
769 | (set! format:pos recursive-pos-save) | |
770 | arg-pos)) ; return the position in the arg. list | |
771 | ||
772 | ;; format:obj->str returns a R4RS representation as a string of an arbitrary | |
773 | ;; scheme object. | |
774 | ;; First parameter is the object, second parameter is a boolean if the | |
775 | ;; representation should be slashified as `write' does. | |
776 | ;; It uses format:char->str which converts a character into | |
777 | ;; a slashified string as `write' does and which is implementation dependent. | |
778 | ;; It uses format:iobj->str to print out internal objects as | |
779 | ;; quoted strings so that the output can always be processed by (read) | |
780 | ||
781 | (define (format:obj->str obj slashify) | |
186cf946 MD |
782 | (define (obj->str obj slashify visited) |
783 | (if (memq obj (cdr visited)) | |
784 | (let ((n (- (list-index (cdr visited) (cdr obj))))) | |
785 | (string-append "#" (number->string n) "#")) | |
786 | (cond | |
787 | ((string? obj) | |
788 | (if slashify | |
789 | (let ((obj-len (string-length obj))) | |
790 | (string-append | |
791 | "\"" | |
792 | (let loop ((i 0) (j 0)) ; taken from Marc Feeley's pp.scm | |
793 | (if (= j obj-len) | |
794 | (string-append (substring obj i j) "\"") | |
795 | (let ((c (string-ref obj j))) | |
796 | (if (or (char=? c #\\) | |
797 | (char=? c #\")) | |
798 | (string-append (substring obj i j) "\\" | |
799 | (loop j (+ j 1))) | |
800 | (loop i (+ j 1)))))))) | |
801 | obj)) | |
7da43e41 | 802 | |
186cf946 | 803 | ((boolean? obj) (if obj "#t" "#f")) |
7da43e41 | 804 | |
186cf946 | 805 | ((number? obj) (number->string obj)) |
7da43e41 | 806 | |
186cf946 MD |
807 | ((symbol? obj) |
808 | (if format:symbol-case-conv | |
809 | (format:symbol-case-conv (symbol->string obj)) | |
810 | (symbol->string obj))) | |
7da43e41 | 811 | |
186cf946 MD |
812 | ((char? obj) |
813 | (if slashify | |
814 | (format:char->str obj) | |
815 | (string obj))) | |
7da43e41 | 816 | |
186cf946 | 817 | ((null? obj) "()") |
7da43e41 | 818 | |
186cf946 MD |
819 | ((input-port? obj) |
820 | (format:iobj->str obj)) | |
7da43e41 | 821 | |
186cf946 MD |
822 | ((output-port? obj) |
823 | (format:iobj->str obj)) | |
7da43e41 | 824 | |
186cf946 MD |
825 | ((pair? obj) |
826 | (string-append "(" | |
827 | (let loop ((obj-list obj) | |
828 | (visited visited) | |
a510a7d8 MD |
829 | (offset 0) |
830 | (prefix "")) | |
186cf946 | 831 | (cond ((null? (cdr obj-list)) |
a510a7d8 MD |
832 | (string-append |
833 | prefix | |
834 | (obj->str (car obj-list) | |
835 | #t | |
836 | (cons (car obj-list) visited)))) | |
186cf946 MD |
837 | ((memq (cdr obj-list) visited) |
838 | (string-append | |
a510a7d8 | 839 | prefix |
186cf946 MD |
840 | (obj->str (car obj-list) |
841 | #t | |
842 | (cons (car obj-list) visited)) | |
843 | " . #" | |
844 | (number->string | |
845 | (- offset | |
846 | (list-index visited (cdr obj-list)))) | |
847 | "#")) | |
848 | ((pair? (cdr obj-list)) | |
a510a7d8 MD |
849 | (loop (cdr obj-list) |
850 | (cons (cdr obj-list) visited) | |
851 | (+ 1 offset) | |
852 | (string-append | |
853 | prefix | |
854 | (obj->str (car obj-list) | |
855 | #t | |
856 | (cons (car obj-list) visited)) | |
857 | " "))) | |
186cf946 MD |
858 | (else |
859 | (string-append | |
a510a7d8 | 860 | prefix |
186cf946 MD |
861 | (obj->str (car obj-list) |
862 | #t | |
863 | (cons (car obj-list) visited)) | |
864 | " . " | |
865 | (obj->str (cdr obj-list) | |
866 | #t | |
867 | (cons (cdr obj-list) visited)))))) | |
868 | ")")) | |
869 | ||
870 | ((vector? obj) | |
871 | (string-append "#" (obj->str (vector->list obj) #t visited))) | |
872 | ||
873 | (else ; only objects with an #<...> | |
874 | (format:iobj->str obj))))) ; representation should fall in here | |
875 | (obj->str obj slashify (list obj))) | |
7da43e41 JB |
876 | |
877 | ;; format:iobj->str reveals the implementation dependent representation of | |
878 | ;; #<...> objects with the use of display and call-with-output-string. | |
879 | ;; If format:read-proof is set to #t the resulting string is additionally | |
880 | ;; set into string quotes. | |
881 | ||
882 | (define format:read-proof #f) | |
883 | ||
884 | (define (format:iobj->str iobj) | |
885 | (if (or format:read-proof | |
886 | format:iobj-case-conv) | |
887 | (string-append | |
888 | (if format:read-proof "\"" "") | |
889 | (if format:iobj-case-conv | |
890 | (format:iobj-case-conv | |
891 | (call-with-output-string (lambda (p) (display iobj p)))) | |
892 | (call-with-output-string (lambda (p) (display iobj p)))) | |
893 | (if format:read-proof "\"" "")) | |
894 | (call-with-output-string (lambda (p) (display iobj p))))) | |
895 | ||
896 | ||
897 | ;; format:char->str converts a character into a slashified string as | |
898 | ;; done by `write'. The procedure is dependent on the integer | |
899 | ;; representation of characters and assumes a character number according to | |
900 | ;; the ASCII character set. | |
901 | ||
902 | (define (format:char->str ch) | |
903 | (let ((int-rep (char->integer ch))) | |
904 | (if (< int-rep 0) ; if chars are [-128...+127] | |
905 | (set! int-rep (+ int-rep 256))) | |
906 | (string-append | |
907 | "#\\" | |
908 | (cond | |
909 | ((char=? ch #\newline) "newline") | |
910 | ((and (>= int-rep 0) (<= int-rep 32)) | |
911 | (vector-ref format:ascii-non-printable-charnames int-rep)) | |
912 | ((= int-rep 127) "del") | |
913 | ((>= int-rep 128) ; octal representation | |
914 | (if format:radix-pref | |
915 | (let ((s (number->string int-rep 8))) | |
916 | (substring s 2 (string-length s))) | |
917 | (number->string int-rep 8))) | |
918 | (else (string ch)))))) | |
919 | ||
920 | (define format:space-ch (char->integer #\space)) | |
921 | (define format:zero-ch (char->integer #\0)) | |
922 | ||
923 | (define (format:par pars length index default name) | |
924 | (if (> length index) | |
925 | (let ((par (list-ref pars index))) | |
926 | (if par | |
927 | (if name | |
928 | (if (< par 0) | |
929 | (format:error | |
930 | "~s parameter must be a positive integer" name) | |
931 | par) | |
932 | par) | |
933 | default)) | |
934 | default)) | |
935 | ||
936 | (define (format:out-obj-padded pad-left obj slashify pars) | |
937 | (if (null? pars) | |
938 | (format:out-str (format:obj->str obj slashify)) | |
939 | (let ((l (length pars))) | |
940 | (let ((mincol (format:par pars l 0 0 "mincol")) | |
941 | (colinc (format:par pars l 1 1 "colinc")) | |
942 | (minpad (format:par pars l 2 0 "minpad")) | |
943 | (padchar (integer->char | |
944 | (format:par pars l 3 format:space-ch #f))) | |
945 | (objstr (format:obj->str obj slashify))) | |
946 | (if (not pad-left) | |
947 | (format:out-str objstr)) | |
948 | (do ((objstr-len (string-length objstr)) | |
949 | (i minpad (+ i colinc))) | |
950 | ((>= (+ objstr-len i) mincol) | |
951 | (format:out-fill i padchar))) | |
952 | (if pad-left | |
953 | (format:out-str objstr)))))) | |
954 | ||
955 | (define (format:out-num-padded modifier number pars radix) | |
956 | (if (not (integer? number)) (format:error "argument not an integer")) | |
957 | (let ((numstr (number->string number radix))) | |
958 | (if (and format:radix-pref (not (= radix 10))) | |
959 | (set! numstr (substring numstr 2 (string-length numstr)))) | |
960 | (if (and (null? pars) (not modifier)) | |
961 | (format:out-str numstr) | |
962 | (let ((l (length pars)) | |
963 | (numstr-len (string-length numstr))) | |
964 | (let ((mincol (format:par pars l 0 #f "mincol")) | |
965 | (padchar (integer->char | |
966 | (format:par pars l 1 format:space-ch #f))) | |
967 | (commachar (integer->char | |
968 | (format:par pars l 2 (char->integer #\,) #f))) | |
969 | (commawidth (format:par pars l 3 3 "commawidth"))) | |
970 | (if mincol | |
971 | (let ((numlen numstr-len)) ; calc. the output len of number | |
972 | (if (and (memq modifier '(at colon-at)) (> number 0)) | |
973 | (set! numlen (+ numlen 1))) | |
974 | (if (memq modifier '(colon colon-at)) | |
975 | (set! numlen (+ (quotient (- numstr-len | |
976 | (if (< number 0) 2 1)) | |
977 | commawidth) | |
978 | numlen))) | |
979 | (if (> mincol numlen) | |
980 | (format:out-fill (- mincol numlen) padchar)))) | |
981 | (if (and (memq modifier '(at colon-at)) | |
982 | (> number 0)) | |
983 | (format:out-char #\+)) | |
984 | (if (memq modifier '(colon colon-at)) ; insert comma character | |
985 | (let ((start (remainder numstr-len commawidth)) | |
986 | (ns (if (< number 0) 1 0))) | |
987 | (format:out-substr numstr 0 start) | |
988 | (do ((i start (+ i commawidth))) | |
989 | ((>= i numstr-len)) | |
990 | (if (> i ns) | |
991 | (format:out-char commachar)) | |
992 | (format:out-substr numstr i (+ i commawidth)))) | |
993 | (format:out-str numstr))))))) | |
994 | ||
995 | (define (format:tabulate modifier pars) | |
996 | (let ((l (length pars))) | |
997 | (let ((colnum (format:par pars l 0 1 "colnum")) | |
998 | (colinc (format:par pars l 1 1 "colinc")) | |
999 | (padch (integer->char (format:par pars l 2 format:space-ch #f)))) | |
1000 | (case modifier | |
1001 | ((colon colon-at) | |
1002 | (format:error "unsupported modifier for ~~t")) | |
1003 | ((at) ; relative tabulation | |
1004 | (format:out-fill | |
1005 | (if (= colinc 0) | |
1006 | colnum ; colnum = colrel | |
1007 | (do ((c 0 (+ c colinc)) | |
1008 | (col (+ format:output-col colnum))) | |
1009 | ((>= c col) | |
1010 | (- c format:output-col)))) | |
1011 | padch)) | |
1012 | (else ; absolute tabulation | |
1013 | (format:out-fill | |
1014 | (cond | |
1015 | ((< format:output-col colnum) | |
1016 | (- colnum format:output-col)) | |
1017 | ((= colinc 0) | |
1018 | 0) | |
1019 | (else | |
1020 | (do ((c colnum (+ c colinc))) | |
1021 | ((>= c format:output-col) | |
1022 | (- c format:output-col))))) | |
1023 | padch)))))) | |
1024 | ||
1025 | ||
1026 | ;; roman numerals (from dorai@cs.rice.edu). | |
1027 | ||
1028 | (define format:roman-alist | |
1029 | '((1000 #\M) (500 #\D) (100 #\C) (50 #\L) | |
1030 | (10 #\X) (5 #\V) (1 #\I))) | |
1031 | ||
1032 | (define format:roman-boundary-values | |
1033 | '(100 100 10 10 1 1 #f)) | |
1034 | ||
1035 | (define format:num->old-roman | |
1036 | (lambda (n) | |
1037 | (if (and (integer? n) (>= n 1)) | |
1038 | (let loop ((n n) | |
1039 | (romans format:roman-alist) | |
1040 | (s '())) | |
1041 | (if (null? romans) (list->string (reverse s)) | |
1042 | (let ((roman-val (caar romans)) | |
1043 | (roman-dgt (cadar romans))) | |
1044 | (do ((q (quotient n roman-val) (- q 1)) | |
1045 | (s s (cons roman-dgt s))) | |
1046 | ((= q 0) | |
1047 | (loop (remainder n roman-val) | |
1048 | (cdr romans) s)))))) | |
1049 | (format:error "only positive integers can be romanized")))) | |
1050 | ||
1051 | (define format:num->roman | |
1052 | (lambda (n) | |
1053 | (if (and (integer? n) (> n 0)) | |
1054 | (let loop ((n n) | |
1055 | (romans format:roman-alist) | |
1056 | (boundaries format:roman-boundary-values) | |
1057 | (s '())) | |
1058 | (if (null? romans) | |
1059 | (list->string (reverse s)) | |
1060 | (let ((roman-val (caar romans)) | |
1061 | (roman-dgt (cadar romans)) | |
1062 | (bdry (car boundaries))) | |
1063 | (let loop2 ((q (quotient n roman-val)) | |
1064 | (r (remainder n roman-val)) | |
1065 | (s s)) | |
1066 | (if (= q 0) | |
1067 | (if (and bdry (>= r (- roman-val bdry))) | |
1068 | (loop (remainder r bdry) (cdr romans) | |
1069 | (cdr boundaries) | |
1070 | (cons roman-dgt | |
1071 | (append | |
1072 | (cdr (assv bdry romans)) | |
1073 | s))) | |
1074 | (loop r (cdr romans) (cdr boundaries) s)) | |
1075 | (loop2 (- q 1) r (cons roman-dgt s))))))) | |
1076 | (format:error "only positive integers can be romanized")))) | |
1077 | ||
1078 | ;; cardinals & ordinals (from dorai@cs.rice.edu) | |
1079 | ||
1080 | (define format:cardinal-ones-list | |
1081 | '(#f "one" "two" "three" "four" "five" | |
1082 | "six" "seven" "eight" "nine" "ten" "eleven" "twelve" "thirteen" | |
1083 | "fourteen" "fifteen" "sixteen" "seventeen" "eighteen" | |
1084 | "nineteen")) | |
1085 | ||
1086 | (define format:cardinal-tens-list | |
1087 | '(#f #f "twenty" "thirty" "forty" "fifty" "sixty" "seventy" "eighty" | |
1088 | "ninety")) | |
1089 | ||
1090 | (define format:num->cardinal999 | |
1091 | (lambda (n) | |
1092 | ;this procedure is inspired by the Bruno Haible's CLisp | |
1093 | ;function format-small-cardinal, which converts numbers | |
1094 | ;in the range 1 to 999, and is used for converting each | |
1095 | ;thousand-block in a larger number | |
1096 | (let* ((hundreds (quotient n 100)) | |
1097 | (tens+ones (remainder n 100)) | |
1098 | (tens (quotient tens+ones 10)) | |
1099 | (ones (remainder tens+ones 10))) | |
1100 | (append | |
1101 | (if (> hundreds 0) | |
1102 | (append | |
1103 | (string->list | |
1104 | (list-ref format:cardinal-ones-list hundreds)) | |
1105 | (string->list" hundred") | |
1106 | (if (> tens+ones 0) '(#\space) '())) | |
1107 | '()) | |
1108 | (if (< tens+ones 20) | |
1109 | (if (> tens+ones 0) | |
1110 | (string->list | |
1111 | (list-ref format:cardinal-ones-list tens+ones)) | |
1112 | '()) | |
1113 | (append | |
1114 | (string->list | |
1115 | (list-ref format:cardinal-tens-list tens)) | |
1116 | (if (> ones 0) | |
1117 | (cons #\- | |
1118 | (string->list | |
1119 | (list-ref format:cardinal-ones-list ones))) | |
1120 | '()))))))) | |
1121 | ||
1122 | (define format:cardinal-thousand-block-list | |
1123 | '("" " thousand" " million" " billion" " trillion" " quadrillion" | |
1124 | " quintillion" " sextillion" " septillion" " octillion" " nonillion" | |
1125 | " decillion" " undecillion" " duodecillion" " tredecillion" | |
1126 | " quattuordecillion" " quindecillion" " sexdecillion" " septendecillion" | |
1127 | " octodecillion" " novemdecillion" " vigintillion")) | |
1128 | ||
1129 | (define format:num->cardinal | |
1130 | (lambda (n) | |
1131 | (cond ((not (integer? n)) | |
1132 | (format:error | |
1133 | "only integers can be converted to English cardinals")) | |
1134 | ((= n 0) "zero") | |
1135 | ((< n 0) (string-append "minus " (format:num->cardinal (- n)))) | |
1136 | (else | |
1137 | (let ((power3-word-limit | |
1138 | (length format:cardinal-thousand-block-list))) | |
1139 | (let loop ((n n) | |
1140 | (power3 0) | |
1141 | (s '())) | |
1142 | (if (= n 0) | |
1143 | (list->string s) | |
1144 | (let ((n-before-block (quotient n 1000)) | |
1145 | (n-after-block (remainder n 1000))) | |
1146 | (loop n-before-block | |
1147 | (+ power3 1) | |
1148 | (if (> n-after-block 0) | |
1149 | (append | |
1150 | (if (> n-before-block 0) | |
1151 | (string->list ", ") '()) | |
1152 | (format:num->cardinal999 n-after-block) | |
1153 | (if (< power3 power3-word-limit) | |
1154 | (string->list | |
1155 | (list-ref | |
1156 | format:cardinal-thousand-block-list | |
1157 | power3)) | |
1158 | (append | |
1159 | (string->list " times ten to the ") | |
1160 | (string->list | |
1161 | (format:num->ordinal | |
1162 | (* power3 3))) | |
1163 | (string->list " power"))) | |
1164 | s) | |
1165 | s)))))))))) | |
1166 | ||
1167 | (define format:ordinal-ones-list | |
1168 | '(#f "first" "second" "third" "fourth" "fifth" | |
1169 | "sixth" "seventh" "eighth" "ninth" "tenth" "eleventh" "twelfth" | |
1170 | "thirteenth" "fourteenth" "fifteenth" "sixteenth" "seventeenth" | |
1171 | "eighteenth" "nineteenth")) | |
1172 | ||
1173 | (define format:ordinal-tens-list | |
1174 | '(#f #f "twentieth" "thirtieth" "fortieth" "fiftieth" "sixtieth" | |
1175 | "seventieth" "eightieth" "ninetieth")) | |
1176 | ||
1177 | (define format:num->ordinal | |
1178 | (lambda (n) | |
1179 | (cond ((not (integer? n)) | |
1180 | (format:error | |
1181 | "only integers can be converted to English ordinals")) | |
1182 | ((= n 0) "zeroth") | |
1183 | ((< n 0) (string-append "minus " (format:num->ordinal (- n)))) | |
1184 | (else | |
1185 | (let ((hundreds (quotient n 100)) | |
1186 | (tens+ones (remainder n 100))) | |
1187 | (string-append | |
1188 | (if (> hundreds 0) | |
1189 | (string-append | |
1190 | (format:num->cardinal (* hundreds 100)) | |
1191 | (if (= tens+ones 0) "th" " ")) | |
1192 | "") | |
1193 | (if (= tens+ones 0) "" | |
1194 | (if (< tens+ones 20) | |
1195 | (list-ref format:ordinal-ones-list tens+ones) | |
1196 | (let ((tens (quotient tens+ones 10)) | |
1197 | (ones (remainder tens+ones 10))) | |
1198 | (if (= ones 0) | |
1199 | (list-ref format:ordinal-tens-list tens) | |
1200 | (string-append | |
1201 | (list-ref format:cardinal-tens-list tens) | |
1202 | "-" | |
1203 | (list-ref format:ordinal-ones-list ones)))) | |
1204 | )))))))) | |
1205 | ||
a0c39db3 MV |
1206 | ;; format inf and nan. |
1207 | ||
1208 | (define (format:out-inf-nan number width digits edigits overch padch) | |
1209 | ;; inf and nan are always printed exactly as "+inf.0", "-inf.0" or | |
1210 | ;; "+nan.0", suitably justified in their field. We insist on | |
1211 | ;; printing this exact form so that the numbers can be read back in. | |
1212 | ||
1213 | (let* ((str (number->string number)) | |
1214 | (len (string-length str)) | |
1215 | (dot (string-index str #\.)) | |
1216 | (digits (+ (or digits 0) | |
1217 | (if edigits (+ edigits 2) 0)))) | |
1218 | (if (and width overch (< width len)) | |
1219 | (format:out-fill width (integer->char overch)) | |
1220 | (let* ((leftpad (if width | |
1221 | (max (- width (max len (+ dot 1 digits))) 0) | |
1222 | 0)) | |
1223 | (rightpad (if width | |
1224 | (max (- width leftpad len) 0) | |
1225 | 0)) | |
1226 | (padch (integer->char (or padch format:space-ch)))) | |
1227 | (format:out-fill leftpad padch) | |
1228 | (format:out-str str) | |
1229 | (format:out-fill rightpad padch))))) | |
1230 | ||
7da43e41 JB |
1231 | ;; format fixed flonums (~F) |
1232 | ||
1233 | (define (format:out-fixed modifier number pars) | |
1234 | (if (not (or (number? number) (string? number))) | |
1235 | (format:error "argument is not a number or a number string")) | |
1236 | ||
1237 | (let ((l (length pars))) | |
1238 | (let ((width (format:par pars l 0 #f "width")) | |
1239 | (digits (format:par pars l 1 #f "digits")) | |
1240 | (scale (format:par pars l 2 0 #f)) | |
1241 | (overch (format:par pars l 3 #f #f)) | |
1242 | (padch (format:par pars l 4 format:space-ch #f))) | |
1243 | ||
a0c39db3 MV |
1244 | (cond |
1245 | ((or (inf? number) (nan? number)) | |
1246 | (format:out-inf-nan number width digits #f overch padch)) | |
1247 | ||
1248 | (digits | |
1249 | (format:parse-float | |
1250 | (if (string? number) number (number->string number)) #t scale) | |
1251 | (if (<= (- format:fn-len format:fn-dot) digits) | |
1252 | (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot))) | |
1253 | (format:fn-round digits)) | |
1254 | (if width | |
1255 | (let ((numlen (+ format:fn-len 1))) | |
1256 | (if (or (not format:fn-pos?) (eq? modifier 'at)) | |
1257 | (set! numlen (+ numlen 1))) | |
1258 | (if (and (= format:fn-dot 0) (> width (+ digits 1))) | |
1259 | (set! numlen (+ numlen 1))) | |
1260 | (if (< numlen width) | |
1261 | (format:out-fill (- width numlen) (integer->char padch))) | |
1262 | (if (and overch (> numlen width)) | |
1263 | (format:out-fill width (integer->char overch)) | |
1264 | (format:fn-out modifier (> width (+ digits 1))))) | |
1265 | (format:fn-out modifier #t))) | |
7da43e41 | 1266 | |
a0c39db3 MV |
1267 | (else |
1268 | (format:parse-float | |
1269 | (if (string? number) number (number->string number)) #t scale) | |
1270 | (format:fn-strip) | |
1271 | (if width | |
1272 | (let ((numlen (+ format:fn-len 1))) | |
1273 | (if (or (not format:fn-pos?) (eq? modifier 'at)) | |
1274 | (set! numlen (+ numlen 1))) | |
1275 | (if (= format:fn-dot 0) | |
1276 | (set! numlen (+ numlen 1))) | |
1277 | (if (< numlen width) | |
1278 | (format:out-fill (- width numlen) (integer->char padch))) | |
1279 | (if (> numlen width) ; adjust precision if possible | |
1280 | (let ((dot-index (- numlen | |
1281 | (- format:fn-len format:fn-dot)))) | |
1282 | (if (> dot-index width) | |
1283 | (if overch ; numstr too big for required width | |
1284 | (format:out-fill width (integer->char overch)) | |
1285 | (format:fn-out modifier #t)) | |
1286 | (begin | |
1287 | (format:fn-round (- width dot-index)) | |
1288 | (format:fn-out modifier #t)))) | |
1289 | (format:fn-out modifier #t))) | |
1290 | (format:fn-out modifier #t))))))) | |
7da43e41 JB |
1291 | |
1292 | ;; format exponential flonums (~E) | |
1293 | ||
1294 | (define (format:out-expon modifier number pars) | |
1295 | (if (not (or (number? number) (string? number))) | |
1296 | (format:error "argument is not a number")) | |
1297 | ||
1298 | (let ((l (length pars))) | |
1299 | (let ((width (format:par pars l 0 #f "width")) | |
1300 | (digits (format:par pars l 1 #f "digits")) | |
1301 | (edigits (format:par pars l 2 #f "exponent digits")) | |
1302 | (scale (format:par pars l 3 1 #f)) | |
1303 | (overch (format:par pars l 4 #f #f)) | |
1304 | (padch (format:par pars l 5 format:space-ch #f)) | |
1305 | (expch (format:par pars l 6 #f #f))) | |
a0c39db3 MV |
1306 | |
1307 | (cond | |
1308 | ((or (inf? number) (nan? number)) | |
1309 | (format:out-inf-nan number width digits edigits overch padch)) | |
1310 | ||
1311 | (digits ; fixed precision | |
7da43e41 JB |
1312 | |
1313 | (let ((digits (if (> scale 0) | |
1314 | (if (< scale (+ digits 2)) | |
1315 | (+ (- digits scale) 1) | |
1316 | 0) | |
1317 | digits))) | |
1318 | (format:parse-float | |
1319 | (if (string? number) number (number->string number)) #f scale) | |
1320 | (if (<= (- format:fn-len format:fn-dot) digits) | |
1321 | (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot))) | |
1322 | (format:fn-round digits)) | |
1323 | (if width | |
1324 | (if (and edigits overch (> format:en-len edigits)) | |
1325 | (format:out-fill width (integer->char overch)) | |
1326 | (let ((numlen (+ format:fn-len 3))) ; .E+ | |
1327 | (if (or (not format:fn-pos?) (eq? modifier 'at)) | |
1328 | (set! numlen (+ numlen 1))) | |
1329 | (if (and (= format:fn-dot 0) (> width (+ digits 1))) | |
1330 | (set! numlen (+ numlen 1))) | |
1331 | (set! numlen | |
1332 | (+ numlen | |
1333 | (if (and edigits (>= edigits format:en-len)) | |
1334 | edigits | |
1335 | format:en-len))) | |
1336 | (if (< numlen width) | |
1337 | (format:out-fill (- width numlen) | |
1338 | (integer->char padch))) | |
1339 | (if (and overch (> numlen width)) | |
1340 | (format:out-fill width (integer->char overch)) | |
1341 | (begin | |
1342 | (format:fn-out modifier (> width (- numlen 1))) | |
1343 | (format:en-out edigits expch))))) | |
1344 | (begin | |
1345 | (format:fn-out modifier #t) | |
a0c39db3 | 1346 | (format:en-out edigits expch))))) |
7da43e41 | 1347 | |
a0c39db3 MV |
1348 | (else |
1349 | (format:parse-float | |
1350 | (if (string? number) number (number->string number)) #f scale) | |
1351 | (format:fn-strip) | |
1352 | (if width | |
1353 | (if (and edigits overch (> format:en-len edigits)) | |
1354 | (format:out-fill width (integer->char overch)) | |
1355 | (let ((numlen (+ format:fn-len 3))) ; .E+ | |
1356 | (if (or (not format:fn-pos?) (eq? modifier 'at)) | |
1357 | (set! numlen (+ numlen 1))) | |
1358 | (if (= format:fn-dot 0) | |
1359 | (set! numlen (+ numlen 1))) | |
1360 | (set! numlen | |
1361 | (+ numlen | |
1362 | (if (and edigits (>= edigits format:en-len)) | |
1363 | edigits | |
1364 | format:en-len))) | |
1365 | (if (< numlen width) | |
1366 | (format:out-fill (- width numlen) | |
1367 | (integer->char padch))) | |
1368 | (if (> numlen width) ; adjust precision if possible | |
1369 | (let ((f (- format:fn-len format:fn-dot))) ; fract len | |
1370 | (if (> (- numlen f) width) | |
1371 | (if overch ; numstr too big for required width | |
1372 | (format:out-fill width | |
1373 | (integer->char overch)) | |
1374 | (begin | |
1375 | (format:fn-out modifier #t) | |
1376 | (format:en-out edigits expch))) | |
1377 | (begin | |
1378 | (format:fn-round (+ (- f numlen) width)) | |
1379 | (format:fn-out modifier #t) | |
1380 | (format:en-out edigits expch)))) | |
1381 | (begin | |
1382 | (format:fn-out modifier #t) | |
1383 | (format:en-out edigits expch))))) | |
1384 | (begin | |
1385 | (format:fn-out modifier #t) | |
1386 | (format:en-out edigits expch)))))))) | |
7da43e41 JB |
1387 | |
1388 | ;; format general flonums (~G) | |
1389 | ||
1390 | (define (format:out-general modifier number pars) | |
1391 | (if (not (or (number? number) (string? number))) | |
1392 | (format:error "argument is not a number or a number string")) | |
1393 | ||
1394 | (let ((l (length pars))) | |
1395 | (let ((width (if (> l 0) (list-ref pars 0) #f)) | |
1396 | (digits (if (> l 1) (list-ref pars 1) #f)) | |
1397 | (edigits (if (> l 2) (list-ref pars 2) #f)) | |
1398 | (overch (if (> l 4) (list-ref pars 4) #f)) | |
1399 | (padch (if (> l 5) (list-ref pars 5) #f))) | |
a0c39db3 MV |
1400 | (cond |
1401 | ((or (inf? number) (nan? number)) | |
1402 | ;; FIXME: this isn't right. | |
1403 | (format:out-inf-nan number width digits edigits overch padch)) | |
1404 | (else | |
1405 | (format:parse-float | |
1406 | (if (string? number) number (number->string number)) #t 0) | |
1407 | (format:fn-strip) | |
1408 | (let* ((ee (if edigits (+ edigits 2) 4)) ; for the following algorithm | |
1409 | (ww (if width (- width ee) #f)) ; see Steele's CL book p.395 | |
1410 | (n (if (= format:fn-dot 0) ; number less than (abs 1.0) ? | |
1411 | (- (format:fn-zlead)) | |
1412 | format:fn-dot)) | |
1413 | (d (if digits | |
1414 | digits | |
1415 | (max format:fn-len (min n 7)))) ; q = format:fn-len | |
1416 | (dd (- d n))) | |
1417 | (if (<= 0 dd d) | |
1418 | (begin | |
1419 | (format:out-fixed modifier number (list ww dd #f overch padch)) | |
1420 | (format:out-fill ee #\space)) ;~@T not implemented yet | |
1421 | (format:out-expon modifier number pars)))))))) | |
7da43e41 JB |
1422 | |
1423 | ;; format dollar flonums (~$) | |
1424 | ||
1425 | (define (format:out-dollar modifier number pars) | |
1426 | (if (not (or (number? number) (string? number))) | |
1427 | (format:error "argument is not a number or a number string")) | |
1428 | ||
1429 | (let ((l (length pars))) | |
1430 | (let ((digits (format:par pars l 0 2 "digits")) | |
1431 | (mindig (format:par pars l 1 1 "mindig")) | |
1432 | (width (format:par pars l 2 0 "width")) | |
1433 | (padch (format:par pars l 3 format:space-ch #f))) | |
1434 | ||
1435 | (format:parse-float | |
1436 | (if (string? number) number (number->string number)) #t 0) | |
1437 | (if (<= (- format:fn-len format:fn-dot) digits) | |
1438 | (format:fn-zfill #f (- digits (- format:fn-len format:fn-dot))) | |
1439 | (format:fn-round digits)) | |
1440 | (let ((numlen (+ format:fn-len 1))) | |
1441 | (if (or (not format:fn-pos?) (memq modifier '(at colon-at))) | |
1442 | (set! numlen (+ numlen 1))) | |
1443 | (if (and mindig (> mindig format:fn-dot)) | |
1444 | (set! numlen (+ numlen (- mindig format:fn-dot)))) | |
1445 | (if (and (= format:fn-dot 0) (not mindig)) | |
1446 | (set! numlen (+ numlen 1))) | |
1447 | (if (< numlen width) | |
1448 | (case modifier | |
1449 | ((colon) | |
1450 | (if (not format:fn-pos?) | |
1451 | (format:out-char #\-)) | |
1452 | (format:out-fill (- width numlen) (integer->char padch))) | |
1453 | ((at) | |
1454 | (format:out-fill (- width numlen) (integer->char padch)) | |
1455 | (format:out-char (if format:fn-pos? #\+ #\-))) | |
1456 | ((colon-at) | |
1457 | (format:out-char (if format:fn-pos? #\+ #\-)) | |
1458 | (format:out-fill (- width numlen) (integer->char padch))) | |
1459 | (else | |
1460 | (format:out-fill (- width numlen) (integer->char padch)) | |
1461 | (if (not format:fn-pos?) | |
1462 | (format:out-char #\-)))) | |
1463 | (if format:fn-pos? | |
1464 | (if (memq modifier '(at colon-at)) (format:out-char #\+)) | |
1465 | (format:out-char #\-)))) | |
1466 | (if (and mindig (> mindig format:fn-dot)) | |
1467 | (format:out-fill (- mindig format:fn-dot) #\0)) | |
1468 | (if (and (= format:fn-dot 0) (not mindig)) | |
1469 | (format:out-char #\0)) | |
1470 | (format:out-substr format:fn-str 0 format:fn-dot) | |
1471 | (format:out-char #\.) | |
1472 | (format:out-substr format:fn-str format:fn-dot format:fn-len)))) | |
1473 | ||
1474 | ; the flonum buffers | |
1475 | ||
e23028a5 | 1476 | (define format:fn-max 400) ; max. number of number digits |
7da43e41 JB |
1477 | (define format:fn-str (make-string format:fn-max)) ; number buffer |
1478 | (define format:fn-len 0) ; digit length of number | |
1479 | (define format:fn-dot #f) ; dot position of number | |
1480 | (define format:fn-pos? #t) ; number positive? | |
1481 | (define format:en-max 10) ; max. number of exponent digits | |
1482 | (define format:en-str (make-string format:en-max)) ; exponent buffer | |
1483 | (define format:en-len 0) ; digit length of exponent | |
1484 | (define format:en-pos? #t) ; exponent positive? | |
1485 | ||
1486 | (define (format:parse-float num-str fixed? scale) | |
1487 | (set! format:fn-pos? #t) | |
1488 | (set! format:fn-len 0) | |
1489 | (set! format:fn-dot #f) | |
1490 | (set! format:en-pos? #t) | |
1491 | (set! format:en-len 0) | |
1492 | (do ((i 0 (+ i 1)) | |
1493 | (left-zeros 0) | |
1494 | (mantissa? #t) | |
1495 | (all-zeros? #t) | |
1496 | (num-len (string-length num-str)) | |
1497 | (c #f)) ; current exam. character in num-str | |
1498 | ((= i num-len) | |
1499 | (if (not format:fn-dot) | |
1500 | (set! format:fn-dot format:fn-len)) | |
1501 | ||
1502 | (if all-zeros? | |
1503 | (begin | |
1504 | (set! left-zeros 0) | |
1505 | (set! format:fn-dot 0) | |
1506 | (set! format:fn-len 1))) | |
1507 | ||
1508 | ;; now format the parsed values according to format's need | |
1509 | ||
1510 | (if fixed? | |
1511 | ||
1512 | (begin ; fixed format m.nnn or .nnn | |
1513 | (if (and (> left-zeros 0) (> format:fn-dot 0)) | |
1514 | (if (> format:fn-dot left-zeros) | |
1515 | (begin ; norm 0{0}nn.mm to nn.mm | |
1516 | (format:fn-shiftleft left-zeros) | |
1517 | (set! left-zeros 0) | |
1518 | (set! format:fn-dot (- format:fn-dot left-zeros))) | |
1519 | (begin ; normalize 0{0}.nnn to .nnn | |
1520 | (format:fn-shiftleft format:fn-dot) | |
1521 | (set! left-zeros (- left-zeros format:fn-dot)) | |
1522 | (set! format:fn-dot 0)))) | |
1523 | (if (or (not (= scale 0)) (> format:en-len 0)) | |
1524 | (let ((shift (+ scale (format:en-int)))) | |
1525 | (cond | |
1526 | (all-zeros? #t) | |
1527 | ((> (+ format:fn-dot shift) format:fn-len) | |
1528 | (format:fn-zfill | |
1529 | #f (- shift (- format:fn-len format:fn-dot))) | |
1530 | (set! format:fn-dot format:fn-len)) | |
1531 | ((< (+ format:fn-dot shift) 0) | |
1532 | (format:fn-zfill #t (- (- shift) format:fn-dot)) | |
1533 | (set! format:fn-dot 0)) | |
1534 | (else | |
1535 | (if (> left-zeros 0) | |
1536 | (if (<= left-zeros shift) ; shift always > 0 here | |
1537 | (format:fn-shiftleft shift) ; shift out 0s | |
1538 | (begin | |
1539 | (format:fn-shiftleft left-zeros) | |
1540 | (set! format:fn-dot (- shift left-zeros)))) | |
1541 | (set! format:fn-dot (+ format:fn-dot shift)))))))) | |
1542 | ||
1543 | (let ((negexp ; expon format m.nnnEee | |
1544 | (if (> left-zeros 0) | |
1545 | (- left-zeros format:fn-dot -1) | |
1546 | (if (= format:fn-dot 0) 1 0)))) | |
1547 | (if (> left-zeros 0) | |
1548 | (begin ; normalize 0{0}.nnn to n.nn | |
1549 | (format:fn-shiftleft left-zeros) | |
1550 | (set! format:fn-dot 1)) | |
1551 | (if (= format:fn-dot 0) | |
1552 | (set! format:fn-dot 1))) | |
1553 | (format:en-set (- (+ (- format:fn-dot scale) (format:en-int)) | |
1554 | negexp)) | |
1555 | (cond | |
1556 | (all-zeros? | |
1557 | (format:en-set 0) | |
1558 | (set! format:fn-dot 1)) | |
1559 | ((< scale 0) ; leading zero | |
1560 | (format:fn-zfill #t (- scale)) | |
1561 | (set! format:fn-dot 0)) | |
1562 | ((> scale format:fn-dot) | |
1563 | (format:fn-zfill #f (- scale format:fn-dot)) | |
1564 | (set! format:fn-dot scale)) | |
1565 | (else | |
1566 | (set! format:fn-dot scale))))) | |
1567 | #t) | |
1568 | ||
1569 | ;; do body | |
1570 | (set! c (string-ref num-str i)) ; parse the output of number->string | |
1571 | (cond ; which can be any valid number | |
1572 | ((char-numeric? c) ; representation of R4RS except | |
1573 | (if mantissa? ; complex numbers | |
1574 | (begin | |
1575 | (if (char=? c #\0) | |
1576 | (if all-zeros? | |
1577 | (set! left-zeros (+ left-zeros 1))) | |
1578 | (begin | |
1579 | (set! all-zeros? #f))) | |
1580 | (string-set! format:fn-str format:fn-len c) | |
1581 | (set! format:fn-len (+ format:fn-len 1))) | |
1582 | (begin | |
1583 | (string-set! format:en-str format:en-len c) | |
1584 | (set! format:en-len (+ format:en-len 1))))) | |
1585 | ((or (char=? c #\-) (char=? c #\+)) | |
1586 | (if mantissa? | |
1587 | (set! format:fn-pos? (char=? c #\+)) | |
1588 | (set! format:en-pos? (char=? c #\+)))) | |
1589 | ((char=? c #\.) | |
1590 | (set! format:fn-dot format:fn-len)) | |
1591 | ((char=? c #\e) | |
1592 | (set! mantissa? #f)) | |
1593 | ((char=? c #\E) | |
1594 | (set! mantissa? #f)) | |
1595 | ((char-whitespace? c) #t) | |
1596 | ((char=? c #\d) #t) ; decimal radix prefix | |
1597 | ((char=? c #\#) #t) | |
1598 | (else | |
1599 | (format:error "illegal character `~c' in number->string" c))))) | |
1600 | ||
1601 | (define (format:en-int) ; convert exponent string to integer | |
1602 | (if (= format:en-len 0) | |
1603 | 0 | |
1604 | (do ((i 0 (+ i 1)) | |
1605 | (n 0)) | |
1606 | ((= i format:en-len) | |
1607 | (if format:en-pos? | |
1608 | n | |
1609 | (- n))) | |
1610 | (set! n (+ (* n 10) (- (char->integer (string-ref format:en-str i)) | |
1611 | format:zero-ch)))))) | |
1612 | ||
1613 | (define (format:en-set en) ; set exponent string number | |
1614 | (set! format:en-len 0) | |
1615 | (set! format:en-pos? (>= en 0)) | |
1616 | (let ((en-str (number->string en))) | |
1617 | (do ((i 0 (+ i 1)) | |
1618 | (en-len (string-length en-str)) | |
1619 | (c #f)) | |
1620 | ((= i en-len)) | |
1621 | (set! c (string-ref en-str i)) | |
1622 | (if (char-numeric? c) | |
1623 | (begin | |
1624 | (string-set! format:en-str format:en-len c) | |
1625 | (set! format:en-len (+ format:en-len 1))))))) | |
1626 | ||
1627 | (define (format:fn-zfill left? n) ; fill current number string with 0s | |
1628 | (if (> (+ n format:fn-len) format:fn-max) ; from the left or right | |
1629 | (format:error "number is too long to format (enlarge format:fn-max)")) | |
1630 | (set! format:fn-len (+ format:fn-len n)) | |
1631 | (if left? | |
1632 | (do ((i format:fn-len (- i 1))) ; fill n 0s to left | |
1633 | ((< i 0)) | |
1634 | (string-set! format:fn-str i | |
1635 | (if (< i n) | |
1636 | #\0 | |
1637 | (string-ref format:fn-str (- i n))))) | |
1638 | (do ((i (- format:fn-len n) (+ i 1))) ; fill n 0s to the right | |
1639 | ((= i format:fn-len)) | |
1640 | (string-set! format:fn-str i #\0)))) | |
1641 | ||
1642 | (define (format:fn-shiftleft n) ; shift left current number n positions | |
1643 | (if (> n format:fn-len) | |
1644 | (format:error "internal error in format:fn-shiftleft (~d,~d)" | |
1645 | n format:fn-len)) | |
1646 | (do ((i n (+ i 1))) | |
1647 | ((= i format:fn-len) | |
1648 | (set! format:fn-len (- format:fn-len n))) | |
1649 | (string-set! format:fn-str (- i n) (string-ref format:fn-str i)))) | |
1650 | ||
1651 | (define (format:fn-round digits) ; round format:fn-str | |
1652 | (set! digits (+ digits format:fn-dot)) | |
1653 | (do ((i digits (- i 1)) ; "099",2 -> "10" | |
1654 | (c 5)) ; "023",2 -> "02" | |
1655 | ((or (= c 0) (< i 0)) ; "999",2 -> "100" | |
1656 | (if (= c 1) ; "005",2 -> "01" | |
1657 | (begin ; carry overflow | |
1658 | (set! format:fn-len digits) | |
1659 | (format:fn-zfill #t 1) ; add a 1 before fn-str | |
1660 | (string-set! format:fn-str 0 #\1) | |
1661 | (set! format:fn-dot (+ format:fn-dot 1))) | |
1662 | (set! format:fn-len digits))) | |
1663 | (set! c (+ (- (char->integer (string-ref format:fn-str i)) | |
1664 | format:zero-ch) c)) | |
1665 | (string-set! format:fn-str i (integer->char | |
1666 | (if (< c 10) | |
1667 | (+ c format:zero-ch) | |
1668 | (+ (- c 10) format:zero-ch)))) | |
1669 | (set! c (if (< c 10) 0 1)))) | |
1670 | ||
1671 | (define (format:fn-out modifier add-leading-zero?) | |
1672 | (if format:fn-pos? | |
1673 | (if (eq? modifier 'at) | |
1674 | (format:out-char #\+)) | |
1675 | (format:out-char #\-)) | |
1676 | (if (= format:fn-dot 0) | |
1677 | (if add-leading-zero? | |
1678 | (format:out-char #\0)) | |
1679 | (format:out-substr format:fn-str 0 format:fn-dot)) | |
1680 | (format:out-char #\.) | |
1681 | (format:out-substr format:fn-str format:fn-dot format:fn-len)) | |
1682 | ||
1683 | (define (format:en-out edigits expch) | |
1684 | (format:out-char (if expch (integer->char expch) format:expch)) | |
1685 | (format:out-char (if format:en-pos? #\+ #\-)) | |
1686 | (if edigits | |
1687 | (if (< format:en-len edigits) | |
1688 | (format:out-fill (- edigits format:en-len) #\0))) | |
1689 | (format:out-substr format:en-str 0 format:en-len)) | |
1690 | ||
1691 | (define (format:fn-strip) ; strip trailing zeros but one | |
1692 | (string-set! format:fn-str format:fn-len #\0) | |
1693 | (do ((i format:fn-len (- i 1))) | |
1694 | ((or (not (char=? (string-ref format:fn-str i) #\0)) | |
1695 | (<= i format:fn-dot)) | |
1696 | (set! format:fn-len (+ i 1))))) | |
1697 | ||
1698 | (define (format:fn-zlead) ; count leading zeros | |
1699 | (do ((i 0 (+ i 1))) | |
1700 | ((or (= i format:fn-len) | |
1701 | (not (char=? (string-ref format:fn-str i) #\0))) | |
1702 | (if (= i format:fn-len) ; found a real zero | |
1703 | 0 | |
1704 | i)))) | |
1705 | ||
1706 | ||
1707 | ;;; some global functions not found in SLIB | |
1708 | ||
7da43e41 JB |
1709 | (define (string-capitalize-first str) ; "hello" -> "Hello" |
1710 | (let ((cap-str (string-copy str)) ; "hELLO" -> "Hello" | |
1711 | (non-first-alpha #f) ; "*hello" -> "*Hello" | |
1712 | (str-len (string-length str))) ; "hello you" -> "Hello you" | |
1713 | (do ((i 0 (+ i 1))) | |
1714 | ((= i str-len) cap-str) | |
1715 | (let ((c (string-ref str i))) | |
1716 | (if (char-alphabetic? c) | |
1717 | (if non-first-alpha | |
1718 | (string-set! cap-str i (char-downcase c)) | |
1719 | (begin | |
1720 | (set! non-first-alpha #t) | |
1721 | (string-set! cap-str i (char-upcase c))))))))) | |
1722 | ||
7da43e41 JB |
1723 | ;; Aborts the program when a formatting error occures. This is a null |
1724 | ;; argument closure to jump to the interpreters toplevel continuation. | |
1725 | ||
8a7391cd | 1726 | (define format:abort (lambda () (error "error in format"))) |
7da43e41 | 1727 | |
1af9072d | 1728 | (define format format:format) |
14469b7c | 1729 | ;; Thanks to Shuji Narazaki |
296ff5e7 | 1730 | (module-set! the-root-module 'format format) |
7da43e41 JB |
1731 | |
1732 | ;; If this is not possible then a continuation is used to recover | |
1733 | ;; properly from a format error. In this case format returns #f. | |
1734 | ||
1735 | ;(define format:abort | |
1736 | ; (lambda () (format:error-continuation #f))) | |
1737 | ||
1738 | ;(define format | |
1739 | ; (lambda args ; wraps format:format with an error | |
1740 | ; (call-with-current-continuation ; continuation | |
1741 | ; (lambda (cont) | |
1742 | ; (set! format:error-continuation cont) | |
1743 | ; (apply format:format args))))) | |
1744 | ||
1745 | ;eof |