Rename *ll* and *ul* to ll and ul in defint-list
[maxima.git] / share / stringproc / printf.lisp
blob67920cdd8dc8375c5e09577285392414e4feab32
1 ;;
2 ;; ~*~ PRINTF ~*~
3 ;;
4 ;; Formatted printing to character streams
5 ;;
6 ;; Copyright : 2005-2015 Volker van Nek
7 ;; Licence : GPL2
8 ;;
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.
29 (in-package :maxima)
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
37 ($printf t "~a " v))
38 (car args) )
41 (defvar $stderr)
42 (defvar $stdout)
43 (defvar $stdin)
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))))
62 'string))
64 (defun $printf (stream ctrls &rest args)
65 (cond
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))
75 (if *tilde-m-args*
76 ;; Capture output from FORMAT, and then call AFORMAT to handle ~M directives.
77 (let*
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
91 params spec subctrls
92 (skip 0) (loops nil) (index 0) )
93 (do ((arg (car todo)))
94 ((null todo)
95 (setq pos1 (search "~" ctrls :start2 start))
96 (when pos1
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
101 (reverse done)
102 (gf-merror (intl:gettext "`printf': arguments exhausted.")) ))
103 (prog ()
104 tag1
105 ;; recognize the directive:
106 (setq pos1 (search "~" ctrls :start2 start))
107 (unless pos1
108 (cond
109 (loop
110 (setq start 0
111 pos1 (search "~" ctrls :start2 start) ))
113 (push arg done)
114 (go tag3) )))
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
125 (do ((p 1 (1+ p)))
126 ((or (= p len) (search ch ",@:v#V")))
127 (setq n (concatenate 'string n ch)
128 ch (subseq params p (1+ p)) ))
129 (setq radix
130 (cond
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:
140 (cond
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))))
147 (incf index)
148 (decf skip) )
149 (push (prepare-arg params spec arg) done)
150 (go tag2) ))
151 ;; ~v,#,vH
152 ((string-equal spec "h")
153 (when (check-v# params)
154 (let ((prms (split-at-comma params))
155 prm (new-params "") )
156 (dolist (p prms)
157 (setq prm
158 (cond
159 ((string-equal "v" p)
160 (prog1
161 ($sconcat arg)
162 (setq arg (car (setq todo (cdr todo))))
163 (incf index) ))
164 ((string= "#" p) ($sconcat (length todo)))
165 (t p) ))
166 (setq new-params (concatenate 'string new-params prm ",")) )
167 (push (prepare-arg new-params spec arg) done)
168 (go tag2) )))
169 ;; ~@[, ~#[, ~n[
170 ((string= "[" spec)
171 (cond
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) )
175 (setq start pos2)
176 (go tag1) )
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
180 ;; ~?
181 ((string= "?" spec)
182 (cond
183 ((string= "" params)
184 (let ((ind-ctrls arg)) ;; arg is a string
185 (push arg done)
186 (setq arg (car (setq todo (cdr todo))))
187 (push (prepare-args ind-ctrls (cdr arg) nil nil) done)
188 (incf index)
189 (go tag2) ))
190 ((string= "@" params)
191 (setq ctrls
192 (concatenate 'string (subseq ctrls 0 pos1) arg (subseq ctrls pos2)) )
193 (push arg done)
194 (go tag3) )
196 (gf-merror (intl:gettext "`printf': illegal directive ~~~m?") params)) ))
197 ;; ~^
198 ((string= "^" spec)
199 (when (search "@" params)
200 (gf-merror (intl:gettext "`printf': illegal directive ~~~m^") params) )
201 (when (> (setq skip (count-v params)) 0)
202 (do () ((= skip 0))
203 (push (if (stringp arg) (character arg) arg) done)
204 (setq arg (car (setq todo (cdr todo))))
205 (incf index)
206 (decf skip) )
207 (push (prepare-arg params spec arg) done) )
208 (setq start pos2)
209 (go tag1) )
210 ;; ~:P and ~:@P
211 ((and (string-equal "p" spec) (search ":" params)) ;; ':' backs up
212 (setq start pos2)
213 (go tag1) ))
214 ;; default part:
216 ;; loop ...
217 (cond
218 ((string= "{" spec)
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)))
223 (do ((p 1 (1+ p)))
224 ((or (= p len)(search ch "@:v#V"))
225 (setq params
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))) )
231 (unless loops
232 (cond
233 ((string-equal ch "v")
234 (push arg done)
235 (incf index)
236 (setq loops arg
237 arg (car (setq todo (cdr todo))) ))
238 ((string= ch "#")
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 ~:@{
245 (cond
246 ((string= "" params)
247 (setq pos2 (cadr (iter-positions ctrls pos1))
248 subctrls (subseq ctrls pos1a (- pos2 2.))
249 loops nil )
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))
254 loops nil )
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)) )
259 (setq done
260 (append
261 (reverse
262 (car
263 (prepare-args
264 subctrls
265 (list (cons '(mlist)
266 (if loops (butlast todo (max 0 (- (length todo) loops))) todo) ))
267 nil
268 nil )))
269 done ))
270 (setq todo (if loops (nthcdr loops todo) nil)
271 arg (car todo)
272 start pos2 )
273 (when loops
274 (incf index loops)
275 (setq loops nil) )
276 (go tag4) )
277 ((string= ":@" params)
278 (setq pos2 (cadr (iter-positions ctrls pos1)))
279 (setq subctrls
280 (concatenate 'string
281 "~" (if loops ($sconcat loops) "") ":{" (subseq ctrls pos1a pos2) ))
282 (setq done
283 (append
284 (reverse
285 (car
286 (prepare-args
287 subctrls
288 (list (cons '(mlist)
289 (if loops (butlast todo (max 0 (- (length todo) loops))) todo) ))
290 nil
291 nil )))
292 done ))
293 (setq todo (if loops (nthcdr loops todo) nil)
294 arg (car todo)
295 start pos2 )
296 (when loops
297 (incf index loops)
298 (setq loops nil) )
299 (go tag4) )))
300 ;; ... or don't loop ...
302 (push (prepare-arg params spec arg) done) ))
303 ;; ... set the position in ctrls ...
304 tag2
305 (setq start pos2)
306 ;; ... and take the next argument
307 tag3
308 (setq arg (car (setq todo (cdr todo))))
309 (incf index)
310 tag4 ))))
313 (defun prepare-ctrls (ctrls)
314 (let ((start 0) (pos1 nil) (pos2 0)
315 (new-ctrls "")
316 spec )
317 ;; ~w,d,e,x,o,p@H
318 (setq pos1 (search "~" ctrls :start2 start))
319 (do ()
320 ((not pos1)
321 (concatenate 'string new-ctrls (subseq ctrls pos2)) )
322 (setq pos2 (spec-position ctrls pos1)
323 spec (subseq ctrls (1- pos2) pos2) )
324 (setq new-ctrls
325 (cond
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)) )))
331 (setq 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)))
340 (return (1+ 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))
349 ((= p len) n)
350 (and (string-equal "v" (subseq params p (1+ p)))
351 (or (zerop p) (not (string= "'" (subseq params (1- p) p))))
352 (setq n (1+ n)) ))))
355 ;; helper for ~v,#,vH
357 (defun check-v# (params)
358 (unless (string= "" params)
359 (do ((p 0 (1+ p)) (len (length params)))
360 ((= p len) nil)
361 (and (search (subseq params p (1+ p)) "vV#")
362 (or (zerop p) (not (string= "'" (subseq params (1- p) p))))
363 (return t) ))))
366 ;; find positions of matching braces
368 (defun iter-positions (ctrls start)
369 (let (pos1 pos2 (end (+ start 2))
370 spec (n 1) )
371 (do ()
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))
378 (setq end pos2) )))
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)))
384 (cond
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)
388 prm "" ))
389 ((and (string= (subseq params p (1+ p)) ",")
390 (not (zerop p))
391 (string= "'" (subseq params (1- p) p)) )
392 (setq prms (cons "," prms)
393 prm "" ))
394 ((string= (subseq params p (1+ p)) "'")
395 nil )
397 (setq prm (concatenate 'string prm (subseq params p (1+ p)))) ))))
400 (defun prepare-arg (params spec arg)
401 (cond
402 ;; ~w,d,e,x,o,p@H
403 ((string-equal "h" spec)
404 (unless (bigfloatp arg)
405 (cond
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.) "'"))
421 (setq at t) )
422 (setq arg
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) ))))
428 ;; ~E, ~F, ~G
429 ((search spec "efg" :test #'string-equal)
430 (unless (floatp arg)
431 (cond
432 (($numberp arg) ;; Maxima rational, bigfloat
433 (setq arg ($float arg)) )
434 ((and ($constantp arg) ;; %pi, sqrt(2), ...
435 (not (member arg '(t nil)))
436 (not ($listp arg))
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 )))))
442 ;; ~D
443 ((string-equal "d" spec)
444 (unless (integerp arg)
445 (cond
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 )))))
454 ;; ~A, ~S
455 ((search spec "as" :test #'string-equal)
456 (setq arg ($sconcat arg)) )
457 ;; ~C
458 ((string-equal "c" spec)
459 (setq arg (character arg)) ) ;; conversion to Lisp char
460 ;; ~M
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)))
467 ;; ~[
468 ((string= "[" spec)
469 (unless (or (string= "@" params) (string= ":" params))
470 (when (integerp arg) (setq arg (1- arg))) ))) ;; 1-based indexing!
471 arg )
474 ;; bf: bigfloat
475 ;; wd: nil or width
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))) ))))
495 (and dd
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)))
506 (len (length part2))
507 (pow (parse-integer (subseq s (1+ pos) nil))) )
508 (cond
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))) ))
513 ((> pow 0)
514 (setq s (cs* part1 part2 (ms* (- pow len) #\0) ".0"))
515 (and dd (> dd 0)
516 (setq s (cs* s (ms* (1- dd) #\0))) ))
517 ((= pow 0)
518 (setq s (cs* part1 (if (= len 0) ".0" ".") part2))
519 (when (= len 0) (incf len))
520 (and dd (> dd len)
521 (setq s (cs* s (ms* (- dd len) #\0))) ))
522 ((< pow 0)
523 (setq pow (- pow)
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))))))
528 (and dd (= dd 0)
529 (setq s (string-right-trim "0" s)))
530 (if (< sgn 0)
531 (setq s (cs* "-" s))
532 (when at (setq s (cs* "+" s))) )
534 (when (or ed xp)
535 (let (xps xpl)
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))
540 (and ed (> ed xpl)
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))) )
547 (and wd (> wd len)
548 (setq pc (if pc (character pc) #\ )
549 s (cs* (ms* (- wd len) pc) s) ))
551 s )))
553 ;; -------------------------------------------------------------------------- ;;