4 ;; Formatted printing to character streams
6 ;; Copyright : 2005-2015 Volker van Nek
9 ;; Test file : rtestprintf.mac (lots of examples)
10 ;; Documentation : stringproc.texi
15 `$printf
' is an interface to the Lisp function
`format
'.
16 `$printf
' provides a directive ~w
,d
,e
,x
,o
,p
@h to process bigfloat numbers.
17 Arguments which are passed to d
,f
,e
,g or h-directives are converted to that type.
19 Before passing to
`format
' ctrls and args are both visited and modified
:
20 E.g.
`prepare-ctrls
' replaces the directive ~w
,d
,e
,x
,o
,p
@h by ~
@a
21 and
`prepare-args
' replaces the corresponding bigfloat argument by a string.
22 As a consequence of this no arg can be used twice. The goto-directive ~
* is lost.
24 In GCL
/utf-8 builds mincol specifications do not work correctly
25 if non-us-ascii characters are used as arguments to ~s and ~a directives.
31 ;; This is an implementation of the formerly src/plot.lisp/$sprint
32 ;; as a simple wrapper for printf(true,"~@{~a ~}",x,y,z,...)
33 ;; keeping the original return value: the first argument resp. false
35 (defun $sprint
(&rest args
)
36 (loop for v in args do
45 ;; Make stdout, stdin and stderr user-accessible
47 (setq $stderr
*error-output
*
48 $stdout
*standard-output
*
49 $stdin
*standard-input
* )
51 (defvar *tilde-m-args
*)
52 (defvar *tilde-m-params
*)
53 (defvar *tilde-m-placeholders
*)
55 (defun reconstitute-tilde-m (param)
56 ($sconcat
"~" param
"m"))
58 (defun generate-tilde-m-placeholder ()
59 (coerce (mapcar #'code-char
60 (loop for i from
1 to
6
61 collect
(if (eq ($random
2) 0) (+ ($random
(- 92 33)) 33) (+ ($random
(- 126 93)) 93))))
64 (defun $printf
(stream ctrls
&rest args
)
66 ((and (not (member stream
'(t nil
))) (not (streamp stream
)))
67 (gf-merror (intl:gettext
68 "`printf': first argument must be `true', `false' or a stream." )))
69 ((not (stringp ctrls
))
70 (gf-merror (intl:gettext
"`printf': second argument must be a string.")) ))
71 (let (*tilde-m-args
* *tilde-m-params
* *tilde-m-placeholders
*)
72 (setq args
(prepare-args ctrls args nil
))
73 (let ((body (mapcar #'(lambda (x) (if (listp x
) `(quote ,x
) x
)) args
)))
74 (setq ctrls
(prepare-ctrls ctrls
))
76 ;; Capture output from FORMAT, and then call AFORMAT to handle ~M directives.
78 ((first-pass (apply 'format nil ctrls body
))
79 (second-pass (ssubst "~~" "~" first-pass
))
80 (third-pass second-pass
))
81 ;; Substitute ~m directives back in.
82 (loop for param in
*tilde-m-params
* for placeholder in
*tilde-m-placeholders
*
83 do
(setq third-pass
(ssubst (reconstitute-tilde-m param
) placeholder third-pass
)))
84 (apply 'aformat stream third-pass
(reverse *tilde-m-args
*)))
85 ;; No ~m directives present, punt directly to FORMAT.
86 (eval `(format ,stream
,ctrls
,@body
)) ))))
89 (defun prepare-args (ctrls todo done
&optional
(loop nil
))
90 (let ((start 0) pos1 pos2 pos1a
92 (skip 0) (loops nil
) (index 0) )
93 (do ((arg (car todo
)))
95 (setq pos1
(search "~" ctrls
:start2 start
))
97 (setq pos2
(spec-position ctrls pos1
)
98 spec
(subseq ctrls
(1- pos2
) pos2
) ))
99 (if (and (zerop skip
) (or (not pos1
) (search spec
"^{}[]<>%;&~tpTP
100 ")));; newline possible spec
102 (gf-merror (intl:gettext
"`printf': arguments exhausted.")) ))
105 ;; recognize the directive:
106 (setq pos1
(search "~" ctrls
:start2 start
))
111 pos1
(search "~" ctrls
:start2 start
) ))
115 (setq pos2
(setq pos1a
(spec-position ctrls pos1
))
116 spec
(subseq ctrls
(1- pos2
) pos2
) )
117 (when (search spec
"}]>;%&t~") (setq start pos2
) (go tag1
))
118 (setq params
(subseq ctrls
(1+ pos1
) (1- pos2
)))
120 ;; pre-test for ~nr, ~vr, ~#r :
121 ;; check if radix is legal (Maxima 5.14 gcl segfaults when radix is 1)
122 (when (and (string-equal spec
"r") (string/= "" params
))
123 (let ((ch (subseq params
0 1)) (n "") (len (length params
)) radix
)
124 (when (or ($digitcharp ch
) (search ch
"v#V")) ;; stringproc.lisp/$digitcharp
126 ((or (= p len
) (search ch
",@:v#V")))
127 (setq n
(concatenate 'string n ch
)
128 ch
(subseq params p
(1+ p
)) ))
131 ((string= ch
",") 10.
)
132 ((string-equal ch
"v") arg
)
133 ((string= ch
"#") (length todo
))
134 ((or (string= ch
"@") (string= ch
":")) (parse-integer n
))
135 (t (parse-integer (concatenate 'string n ch
))) ))
136 (when (or (< radix
2.
) (> radix
36.
))
137 (gf-merror (intl:gettext
"`printf': illegal radix in r-directive: ~m") radix
)) )))
139 ;; handle some special directives:
141 ;; ~v,v,v<spec>, spec=ABDEFGORSTX<~&%$ (# needs no special care; ~v[ not supported, see below)
142 ((search spec
"abdefgorstx<~&%$" :test
#'string-equal
)
143 (when (> (setq skip
(count-v params
)) 0)
144 (do () ((zerop skip
))
145 (push (if (stringp arg
) (character arg
) arg
) done
)
146 (setq arg
(car (setq todo
(cdr todo
))))
149 (push (prepare-arg params spec arg
) done
)
152 ((string-equal spec
"h")
153 (when (check-v# params
)
154 (let ((prms (split-at-comma params
))
155 prm
(new-params "") )
159 ((string-equal "v" p
)
162 (setq arg
(car (setq todo
(cdr todo
))))
164 ((string= "#" p
) ($sconcat
(length todo
)))
166 (setq new-params
(concatenate 'string new-params prm
",")) )
167 (push (prepare-arg new-params spec arg
) done
)
172 ((string= "" params
)) ;; don't check another condition
173 ((or (and (string= "@" params
) arg
) ;; if arg is not nil, arg is not consumed
174 (string= "#" params
) )
177 ((or (string= "v" params
)
178 (every #'digit-char-p
(coerce params
'list
)))
179 (gf-merror (intl:gettext
"`printf': not supported directive ~~~m[") params
) ))) ;; 0- vs. 1-based indexing
184 (let ((ind-ctrls arg
)) ;; arg is a string
186 (setq arg
(car (setq todo
(cdr todo
))))
187 (push (prepare-args ind-ctrls
(cdr arg
) nil nil
) done
)
190 ((string= "@" params
)
192 (concatenate 'string
(subseq ctrls
0 pos1
) arg
(subseq ctrls pos2
)) )
196 (gf-merror (intl:gettext
"`printf': illegal directive ~~~m?") params
)) ))
199 (when (search "@" params
)
200 (gf-merror (intl:gettext
"`printf': illegal directive ~~~m^") params
) )
201 (when (> (setq skip
(count-v params
)) 0)
203 (push (if (stringp arg
) (character arg
) arg
) done
)
204 (setq arg
(car (setq todo
(cdr todo
))))
207 (push (prepare-arg params spec arg
) done
) )
211 ((and (string-equal "p" spec
) (search ":" params
)) ;; ':' backs up
219 ;; ~n{ and ~v{ etc. , set number of loops
220 (and (string/= "" params
)
221 (string/= ":" params
) (string/= "@" params
) (string/= ":@" params
)
222 (let ((ch (subseq params
0 1)) (n "") (len (length params
)))
224 ((or (= p len
)(search ch
"@:v#V"))
226 (if (or (string= ch
"@") (string= ch
":"))
227 (subseq params
(1- p
))
228 (subseq params p
) )))
229 (setq n
(concatenate 'string n ch
)
230 ch
(subseq params p
(1+ p
))) )
233 ((string-equal ch
"v")
237 arg
(car (setq todo
(cdr todo
))) ))
239 (setq loops
(length todo
)) )
240 ((or (string= ch
"@") (string= ch
":"))
241 (setq loops
(parse-integer n
)) )
243 (setq loops
(parse-integer (concatenate 'string n ch
))) ))) ))
244 ;; ~{ and ~:{ and ~@{ and ~:@{
247 (setq pos2
(cadr (iter-positions ctrls pos1
))
248 subctrls
(subseq ctrls pos1a
(- pos2
2.
))
250 (push (prepare-args subctrls
(cdr arg
) nil t
) done
) )
251 ((string= ":" params
)
252 (setq pos2
(cadr (iter-positions ctrls pos1
))
253 subctrls
(concatenate 'string
"~{" (subseq ctrls pos1a pos2
))
255 (push (prepare-args subctrls
(cdr arg
) nil t
) done
) )
256 ((string= "@" params
)
257 (setq pos2
(cadr (iter-positions ctrls pos1
))
258 subctrls
(concatenate 'string
"~{" (subseq ctrls pos1a pos2
)) )
266 (if loops
(butlast todo
(max 0 (- (length todo
) loops
))) todo
) ))
270 (setq todo
(if loops
(nthcdr loops todo
) nil
)
277 ((string= ":@" params
)
278 (setq pos2
(cadr (iter-positions ctrls pos1
)))
281 "~" (if loops
($sconcat loops
) "") ":{" (subseq ctrls pos1a pos2
) ))
289 (if loops
(butlast todo
(max 0 (- (length todo
) loops
))) todo
) ))
293 (setq todo
(if loops
(nthcdr loops todo
) nil
)
300 ;; ... or don't loop ...
302 (push (prepare-arg params spec arg
) done
) ))
303 ;; ... set the position in ctrls ...
306 ;; ... and take the next argument
308 (setq arg
(car (setq todo
(cdr todo
))))
313 (defun prepare-ctrls (ctrls)
314 (let ((start 0) (pos1 nil
) (pos2 0)
318 (setq pos1
(search "~" ctrls
:start2 start
))
321 (concatenate 'string new-ctrls
(subseq ctrls pos2
)) )
322 (setq pos2
(spec-position ctrls pos1
)
323 spec
(subseq ctrls
(1- pos2
) pos2
) )
326 ((string-equal spec
"h")
327 (concatenate 'string new-ctrls
(subseq ctrls start pos1
) "~@a"))
328 ((string-equal spec
"m")
329 (concatenate 'string new-ctrls
(subseq ctrls start pos1
) "~a"))
330 (t (concatenate 'string new-ctrls
(subseq ctrls start pos2
)) )))
332 pos1
(search "~" ctrls
:start2 start
) ))))
335 (defun spec-position (ctrls pos1
)
336 (do ((p (1+ pos1
) (1+ p
))) (())
337 (and (search (subseq ctrls p
(1+ p
)) "abcdefghmoprstx%{}^&$[]?~<>;ABCDEFGHMOPRSTX
338 ") ;; newline possible spec
339 (not (string= "'" (subseq ctrls
(1- p
) p
)))
343 ;; helper for ~v,v,v<spec>
345 (defun count-v (params)
346 (if (string= "" params
)
348 (do ((p 0 (1+ p
)) (len (length params
)) (n 0))
350 (and (string-equal "v" (subseq params p
(1+ p
)))
351 (or (zerop p
) (not (string= "'" (subseq params
(1- p
) p
))))
355 ;; helper for ~v,#,vH
357 (defun check-v# (params)
358 (unless (string= "" params
)
359 (do ((p 0 (1+ p
)) (len (length params
)))
361 (and (search (subseq params p
(1+ p
)) "vV#")
362 (or (zerop p
) (not (string= "'" (subseq params
(1- p
) p
))))
366 ;; find positions of matching braces
368 (defun iter-positions (ctrls start
)
369 (let (pos1 pos2
(end (+ start
2))
372 ((zerop n
) (list start end
))
373 (setq pos1
(search "~" ctrls
:start2 end
)
374 pos2
(spec-position ctrls pos1
)
375 spec
(subseq ctrls
(1- pos2
) pos2
) )
376 (when (string-equal spec
"{") (incf n
))
377 (when (string-equal spec
"}") (decf n
))
381 (defun split-at-comma (params)
382 (do ((p 0 (1+ p
)) (len (length params
)) (prms nil
) (prm ""))
383 ((= p len
) (reverse (cons prm prms
)))
385 ((and (search (subseq params p
(1+ p
)) ",@")
386 (or (zerop p
) (not (string= "'" (subseq params
(1- p
) p
)))) )
387 (setq prms
(cons prm prms
)
389 ((and (string= (subseq params p
(1+ p
)) ",")
391 (string= "'" (subseq params
(1- p
) p
)) )
392 (setq prms
(cons "," prms
)
394 ((string= (subseq params p
(1+ p
)) "'")
397 (setq prm
(concatenate 'string prm
(subseq params p
(1+ p
)))) ))))
400 (defun prepare-arg (params spec arg
)
403 ((string-equal "h" spec
)
404 (unless (bigfloatp arg
)
406 (($numberp arg
) ;; Maxima rational, float
407 (setq arg
($bfloat arg
)) )
408 ((and ($constantp arg
) ;; %pi, sqrt(2), ...
409 ($freeof
'$%i arg
) (not (member arg
'(t nil
))) (not ($listp arg
)))
410 (setq arg
($bfloat arg
)) )
412 (gf-merror (intl:gettext
413 "`printf': argument can't be supplied to h-directive: ~m" ) arg
))))
414 (let ((prms (split-at-comma params
))
415 wd dd ed xp ov pc at smarap
)
416 (multiple-value-setq (wd dd ed xp ov pc
) (apply #'values prms
))
417 (setq smarap
(reverse params
))
418 (and (string/= "" smarap
)
419 (string= (subseq smarap
0 1) "@")
420 (or (= 1 (length smarap
)) (string/= (subseq smarap
1 2.
) "'"))
423 (let ((wd (and wd
(string/= "" wd
) (parse-integer wd
)))
424 (dd (and dd
(string/= "" dd
) (parse-integer dd
)))
425 (ed (and ed
(string/= "" ed
) (parse-integer ed
)))
426 (xp (and xp
(string/= "" xp
) (parse-integer xp
))) )
427 (bprintf arg wd dd ed xp ov pc at
) ))))
429 ((search spec
"efg" :test
#'string-equal
)
432 (($numberp arg
) ;; Maxima rational, bigfloat
433 (setq arg
($float arg
)) )
434 ((and ($constantp arg
) ;; %pi, sqrt(2), ...
435 (not (member arg
'(t nil
)))
437 (let ((would-be-arg ($float arg
)))
438 (and ($numberp would-be-arg
) (setq arg would-be-arg
)))))
440 (gf-merror (intl:gettext
441 "`printf': argument can't be supplied to ~m-directive: ~m" ) spec arg
)))))
443 ((string-equal "d" spec
)
444 (unless (integerp arg
)
446 (($numberp arg
) ;; Maxima rational, (big)float
447 (setq arg
($truncate arg
)) )
448 ((and ($constantp arg
) ;; %pi, sqrt(2), ...
449 ($freeof
'$%i arg
) (not (member arg
'(t nil
))) (not ($listp arg
)))
450 (setq arg
($truncate arg
)) )
452 (gf-merror (intl:gettext
453 "`printf': argument can't be supplied to d-directive: ~m" ) arg
)))))
455 ((search spec
"as" :test
#'string-equal
)
456 (setq arg
($sconcat arg
)) )
458 ((string-equal "c" spec
)
459 (setq arg
(character arg
)) ) ;; conversion to Lisp char
461 ((string-equal "m" spec
)
462 (push arg
*tilde-m-args
*)
463 (push params
*tilde-m-params
*)
464 (let ((placeholder (generate-tilde-m-placeholder)))
465 (push placeholder
*tilde-m-placeholders
*)
466 (setq arg placeholder
)))
469 (unless (or (string= "@" params
) (string= ":" params
))
470 (when (integerp arg
) (setq arg
(1- arg
))) ))) ;; 1-based indexing!
476 ;; dd: nil or decimal digits behind floating point
477 ;; ed: nil or minimal exponent digits
478 ;; xp: nil or preferred exponent
479 ;; ov: nil or overflow character
480 ;; pc: nil or padding character
481 ;; at: nil or true; sets "+" if true
483 (defun bprintf (bf wd dd ed xp ov pc at
);; ~w,d,e,x,o,p@H
485 (labels ((cs* (&rest args
) (eval `(concatenate 'string
,@args
))) ;; functions to shortcut
486 (ms* (len ie
) (make-string len
:initial-element ie
)) ) ;; some parts of the code
487 (declare (inline cs
* ms
*))
489 (let ((fpprec (caddar bf
))) ;; keep old fpprec in case it differs to current fpprec
490 (and xp
(not (zerop xp
))
491 (setq bf
(bcons (if (minusp xp
)
492 (fptimes* (cdr bf
) (intofp (expt 10.
(- xp
))))
493 (fpquotient (cdr bf
) (intofp (expt 10. xp
))) ))))
496 (let ((m (intofp (expt 10. dd
))))
497 (setq bf
(fptimes* (cdr bf
) m
)
498 bf
(meval `((%round
) ,(bcons bf
)))
499 bf
(bcons (fpquotient (intofp bf
) m
)) ))))
501 (let* ((s (string-left-trim "-" ($sconcat bf
)))
502 (sgn (signum (cadr bf
)))
503 (part1 (subseq s
0 1))
504 (pos (position #\b s
))
505 (part2 (string-right-trim "0" (subseq s
2. pos
)))
507 (pow (parse-integer (subseq s
(1+ pos
) nil
))) )
509 ((and (> pow
0) (> len pow
))
510 (setq s
(cs* part1
(subseq part2
0 pow
) "." (subseq part2 pow nil
)))
511 (and dd
(> (+ dd pow
) len
)
512 (setq s
(cs* s
(ms* (+ dd pow
(- len
)) #\
0))) ))
514 (setq s
(cs* part1 part2
(ms* (- pow len
) #\
0) ".0"))
516 (setq s
(cs* s
(ms* (1- dd
) #\
0))) ))
518 (setq s
(cs* part1
(if (= len
0) ".0" ".") part2
))
519 (when (= len
0) (incf len
))
521 (setq s
(cs* s
(ms* (- dd len
) #\
0))) ))
524 s
(cs* "0." (ms* (1- pow
) #\
0) part1 part2
) )
525 (and dd
(> dd
(+ len pow
))
526 (setq s
(cs* s
(ms* (- dd pow len
) #\
0))))))
529 (setq s
(string-right-trim "0" s
)))
532 (when at
(setq s
(cs* "+" s
))) )
536 (unless xp
(setq xp
0))
537 (setq xps
(mfuncall '$string xp
))
538 (when (< xp
0) (setq xps
(subseq xps
1)))
539 (setq xpl
(length xps
))
541 (setq xps
(cs* (ms* (- ed xpl
) #\
0) xps
)) )
542 (setq s
(cs* s
"b" (if (< xp
0) "-" "+") xps
)) ))
544 (setq len
(length s
))
545 (and wd ov
(= (length ov
) 1) (> len wd
)
546 (setq s
(ms* wd
(character ov
))) )
548 (setq pc
(if pc
(character pc
) #\
)
549 s
(cs* (ms* (- wd len
) pc
) s
) ))
553 ;; -------------------------------------------------------------------------- ;;