Coverage report: /home/ellis/comp/core/std/fmt.lisp
Kind | Covered | All | % |
expression | 16 | 978 | 1.6 |
branch | 0 | 52 | 0.0 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; std/fmt.lisp --- printer and format utils
6
(defun iprintln (x &optional (n 2) stream)
7
"Print object X with indentation N to stream followed by a new line."
8
(println (format nil "~A~A" (make-string n :initial-element #\Space) x) stream))
10
(defun fmt-row (data &optional stream)
11
"Format DATA as a table row to STREAM."
12
(format stream "| ~{~A~^ | ~} |~%" data))
14
(defun printer-status ()
15
"Return the current printer status."
16
(macrolet ((fmt (var) `(list ',var ,var)))
29
(fmt *print-miser-width*)
30
(fmt *print-pprint-dispatch*)
33
(fmt *print-readably*)
34
(fmt *print-right-margin*)))))
36
(defun format-sxhash (code &optional stream)
37
"Turn the fixnum value CODE into a human-friendly string. CODE should
38
be produced by `sxhash'."
41
(push (ldb (byte 8 (* i 8)) code) r))
46
(lambda (x) (format nil "~{~(~2,'0x~)~}" x))
51
;; from https://gist.github.com/WetHat/9682b8f70f0241c37cd5d732784d1577
55
;; (let ((tree '(A B1 B2 (B3 C1) C2)))
56
;; ; enumerate all layout options and draw the tree for each one.
57
;; (dolist (layout '(:up :centered :down))
58
;; (format t "Layout = :~A~%" layout)
59
;; (fmt-tree t tree :layout layout)))
83
;; Unicode plain ASCII representation
85
(defvar *upper-knee* " ╭─ ") ; " .- "
86
(defvar *pipe* " │ ") ; " | "
87
(defvar *tee* " ├─ ") ; " +- "
88
(defvar *lower-knee* " ╰─ ") ; " '- "
90
(defun format-tree-segments (node &key (layout :centered)
91
(node-formatter #'write-to-string))
92
"Format the tree-segments of NODE."
94
(return-from format-tree-segments nil)) ; nothing to do here
95
(setq node (ensure-cons node))
96
(flet ((prefix-node-strings (child-node &key layout node-formatter
97
(upper-connector *pipe*)
98
(root-connector *tee*)
99
(lower-connector *pipe*))
100
"A local utility to add connectors to a string representation
101
of a tree segment to connect it to other tree segments."
102
(multiple-value-bind (u r l)
103
(format-tree-segments child-node
105
:node-formatter node-formatter)
106
; prefix tree segment with connector glyphs to connect it to
110
(lambda (str) (concatenate 'string upper-connector str))
112
(list (concatenate 'string root-connector r))
114
(lambda (str) (concatenate 'string lower-connector str))
116
(let* ((children (rest node))
117
(pivot (case layout ; the split point of the list of children
118
(:up (length children)) ; split at top
119
(:down 0) ; split at bottom
120
(otherwise (round (/ (length children) 2))))) ; bisect
121
(upper-children (reverse (subseq children 0 pivot))) ; above root
122
(lower-children (subseq children pivot))) ; nodes below root
123
(values ; compile multiple value return of upper-children root lower children
125
(loop with top = (prefix-node-strings (first upper-children)
127
:node-formatter node-formatter
128
:upper-connector *space*
129
:root-connector *upper-knee*) ; top node has special connectors
130
for child-node in (rest upper-children)
131
nconc (prefix-node-strings child-node
133
:node-formatter node-formatter)
135
finally (return (nconc top strlist))))
136
(let ((root-name (funcall node-formatter (car node)))) ; root node
137
(if (= 1 (length root-name))
138
(concatenate 'string " " root-name) ; at least 2 chars needed
142
(loop for (head . tail) on lower-children
143
while tail ; omit the last child
144
nconc (prefix-node-strings head
146
:node-formatter node-formatter)
151
; bottom node has special connectors
152
(prefix-node-strings head
154
:node-formatter node-formatter
155
:root-connector *lower-knee*
156
:lower-connector *space*)))))))))
158
(defun fmt-tree (stream root &key
161
(node-formatter #'write-to-string))
162
"Format ROOT as a tree of nodes, printing to STREAM."
163
(multiple-value-bind (u r l)
164
(format-tree-segments (if plist (cons (car root) (group (cdr root) 2)) root)
166
:node-formatter node-formatter)
167
(format stream "~{~A~%~}" (nconc u (list r) l))))
169
(defun human-readable-size (number)
170
(check-type number integer)
171
(loop for size in '(80 70 60 50 40 30 20 10)
172
and unit in '("YB" "ZB" "EB" "PB" "TB" "GB" "MB" "KB")
173
when (> (ash number (- size)) 0)
174
do (return-from human-readable-size
175
(format nil "~,2F ~A"
176
(float (/ number (ash 1 size)))
180
(defvar *print-slot-indent* 0)
182
(defun describe-slot (name value &optional (max-slot-name-length 30) (stream t) (indent *print-slot-indent*))
183
"Describe slot NAME with associated VALUE."
184
(format stream "~%~A~VA = ~A" (make-string indent :initial-element #\space) max-slot-name-length name (prin1-to-line value)))
186
;; FROM: sb-impl describe
187
(defun %describe-object (object stream)
188
(let* ((class (class-of object))
189
(slotds (sb-mop:class-slots class))
190
(max-slot-name-length 30)
192
;; Figure out a good width for the slot-name column.
193
(flet ((adjust-slot-name-length (name)
194
(setf max-slot-name-length
195
(max max-slot-name-length (length (symbol-name name))))))
196
(dolist (slotd slotds)
197
(adjust-slot-name-length (sb-mop:slot-definition-name slotd))
198
(push slotd (getf plist (sb-mop:slot-definition-allocation slotd)))))
199
;; Now that we know the width, we can print.
200
(sb-int:doplist (allocation slots) plist
201
(dolist (slotd (nreverse slots))
203
(sb-mop:slot-definition-name slotd)
204
(sb-pcl::slot-value-for-printing object (sb-mop:slot-definition-name slotd)))))
206
(format stream "~@:_No slots."))))
208
(defun print-slots (object &optional (stream t))
209
"Print the slots of OBJECT to STREAM."
210
(let ((*print-right-margin* (or *print-right-margin* 72))
212
(*print-circle-not-shared* t)
214
(*suppress-print-errors*
215
(if (subtypep 'serious-condition *suppress-print-errors*)
216
*suppress-print-errors*
217
'serious-condition)))
218
(%describe-object object stream)))
220
(defun format-slots (stream &rest slots)
221
"Print SLOTS to STREAM."
222
(let ((*print-right-margin* (or *print-right-margin* 72))
224
(*print-circle-not-shared* t)
226
(*suppress-print-errors*
227
(if (subtypep 'serious-condition *suppress-print-errors*)
228
*suppress-print-errors*
229
'serious-condition)))
230
(sb-int:doplist (k v) (print slots)
231
(describe-slot (string k) v 30 stream))
232
(force-output stream)))
236
;; These bits of lovely code are sourced from here:
237
;; https://github.com/whalliburton/academy/blob/87a1a13ffbcd60d8553e42e647c59486c761e8cf/drawing.lisp
238
(defun make-bitmap (width height &optional contents)
240
(make-array (list height width) :initial-contents contents)
241
(make-array (list height width) :initial-element nil)))
244
(defvar *bitmap-overwrite* nil)
246
(defmacro with-bitmap ((width height) &body body)
247
`(let ((*bitmap* (or *bitmap-overwrite* (make-bitmap ,width ,height))))
250
(defun outside-bounds (x y &optional (bitmap *bitmap*))
251
(destructuring-bind (height width) (array-dimensions bitmap)
252
(or (< x 0) (< y 0) (>= x width) (>= y height))))
254
(defun set-pixel (x y &optional (bitmap *bitmap*) (value t))
255
(unless (outside-bounds x y bitmap)
256
(setf (aref bitmap y x) value)))
258
(defvar *comic-strip*)
259
(defvar *save-drawing-name* nil)
261
(defun draw (&optional (bitmap *bitmap*))
263
((and (boundp '*comic-strip*) (not (eq bitmap (comic-strip-bitmap *comic-strip*))))
264
(draw-on-comic-strip *comic-strip* bitmap))
265
(t (destructuring-bind (height width) (array-dimensions bitmap)
266
(loop for y from 0 to (1- height) by 2
267
do (loop for x from 0 to (1- width)
269
(let ((top (aref bitmap y x))
270
(bottom (when (< y (1- height)) (aref bitmap (1+ y) x))))
272
((or (stringp top) (stringp bottom))
273
(incf x (length (or top bottom)))
275
((and top bottom) #\FULL_BLOCK)
276
(top #\UPPER_HALF_BLOCK)
277
(bottom #\LOWER_HALF_BLOCK )
282
(defun draw-from-list (bit-list width)
283
(let ((rows (group bit-list width)))
284
(draw (make-array (list (length rows) width) :initial-contents rows))))
286
(defun copy-onto-bitmap (bitmap pattern x y)
287
(loop for row in pattern
289
do (loop for character across row
291
do (setf (aref bitmap yi xi) (not (eq character #\space)))))
294
(defun center-on-bitmap (bitmap pattern)
295
(destructuring-bind (height width) (array-dimensions bitmap)
296
(copy-onto-bitmap bitmap pattern
297
(- (floor width 2) (floor (length (car pattern)) 2))
298
(- (floor height 2) (floor (length pattern) 2)))))
300
(defun pattern-to-bitmap (pattern)
301
(let ((bitmap (make-bitmap (length (car pattern)) (length pattern))))
302
(loop for row in pattern
304
do (loop for character in (coerce row 'list)
306
do (when (not (char= character #\space))
307
(setf (aref bitmap y x) t))))
311
"When you're smiling, the whole world smiles with you."
312
(draw (pattern-to-bitmap '(" **** "
321
(defun draw-border (&optional (bitmap *bitmap*))
322
(destructuring-bind (height width) (array-dimensions bitmap)
323
(loop for x from 0 to (1- width)
324
do (setf (aref bitmap 0 x) t
325
(aref bitmap (1- height) x) t))
326
(loop for y from 1 to (- height 2)
327
do (setf (aref bitmap y 0) t
328
(aref bitmap y (1- width)) t)))
331
;;; Computer Graphics - Principles and Practice by Donald Hearn and M. Pauline Baker
332
(defun draw-circle (x-center y-center radius &optional (bitmap *bitmap*))
333
(labels ((pixel (x y) (set-pixel (+ x-center x) (+ y-center y) bitmap))
342
(pixel (- y) (- x))))
345
with p = (- 1 radius)
346
initially (draw-points x y)
350
(incf p (+ (* 2 x) 1))
353
(incf p (+ (* 2 (- x y)) 1))))
356
(defun bullseye (&key (size 64) (step 4) filled (draw t))
358
(with-bitmap (size size)
359
(let ((mid (floor size 2)))
360
(loop for radius from 2 to mid by step
361
do (draw-circle mid mid radius))
363
(loop for x from 2 to mid by (* 2 step)
364
do (fill-bitmap (+ mid x 1) mid)))
369
(defun moire (&key (size 64) (step 4) (filled t) (offset 16))
370
"Draw a Moiré pattern."
371
(let ((*bitmap* (make-bitmap (+ size offset) size))
372
(one (bullseye :size size :step step :filled filled :draw nil)))
373
(copy-bitmap-onto-bitmap one *bitmap* 0 0)
374
(copy-bitmap-onto-bitmap one *bitmap* offset 0)
377
(defun draw-line (xa ya xb yb &optional (bitmap *bitmap*))
378
(let* ((dx (- xb xa))
380
(steps (if (> (abs dx) (abs dy)) (abs dx) (abs dy)))
383
(set-pixel xa ya bitmap)
386
for k from 0 to (1- steps)
389
(set-pixel (floor x) (floor y) bitmap))))
391
(defun sunbeam (&key (step 8) (size 64))
393
(with-bitmap (size size)
394
(loop for x from 0 to size by step
395
do (draw-line 0 (1- size) x 0)
396
(draw-line 0 (1- size) (1- size) x))
399
(defun fill-bitmap (x y &optional (bitmap *bitmap*))
400
(unless (outside-bounds x y bitmap)
401
(unless (aref bitmap y x)
402
(setf (aref bitmap y x) t)
403
(fill-bitmap (+ x 1) y bitmap)
404
(fill-bitmap (- x 1) y bitmap)
405
(fill-bitmap x (+ y 1) bitmap)
406
(fill-bitmap x (- y 1) bitmap))))
408
(defun draw-filled-circle (x-center y-center radius &optional (bitmap *bitmap*))
409
(draw-circle x-center y-center radius bitmap)
410
(fill-bitmap x-center y-center bitmap))
412
(defun sun (&key (size 64))
414
(with-bitmap (size size)
415
(let ((mid (floor size 2)))
416
(draw-filled-circle mid mid (1- mid))
419
;;; Attention Hackers! Exercises are good for the soul.
421
;;; Someone with the desire could expand PEACE to draw peace symbols of any size.
432
(defun copy-bitmap-onto-bitmap (from-bitmap to-bitmap x y &key (fn (lambda (a b) (or a b))))
433
(destructuring-bind (height width) (array-dimensions from-bitmap)
434
(loop for yi from 0 to (1- height)
435
do (loop for xi from 0 to (1- width)
436
do (let ((from (aref from-bitmap yi xi))
437
(to (aref to-bitmap (+ y yi) (+ x xi))))
438
(set-pixel (+ x xi) (+ y yi)
440
(funcall fn from to)))))))
442
(defun center-bitmap-onto-bitmap (from-bitmap to-bitmap)
443
(destructuring-bind (fh fw) (array-dimensions from-bitmap)
444
(destructuring-bind (th tw) (array-dimensions to-bitmap)
445
(copy-bitmap-onto-bitmap from-bitmap to-bitmap
447
(floor (- th fh) 2)))))
449
(defstruct comic-strip bitmap width height columns rows column)
451
(defmacro with-comic-strip ((&key (width 32) (height 32) (columns 3) (action 'draw)) &body body)
452
`(let ((*comic-strip* (make-comic-strip :bitmap (make-bitmap (* ,width ,columns) 0)
453
:width ,width :height ,height :columns ,columns
456
(,action (comic-strip-bitmap *comic-strip*))))
458
(defun draw-on-comic-strip (strip cell-bitmap)
459
(with-slots (rows column width height columns bitmap) strip
463
(adjust-array bitmap (list (* rows height) (* columns width)) :initial-element nil)))
464
(copy-bitmap-onto-bitmap cell-bitmap bitmap (* column width) (* (1- rows) height))
465
(setf column (mod (1+ column) columns))))
467
(defun plot-function (fn start end &optional (width 64) (height 32))
468
"Show a graph of FN of size WIDTHxHEIGHT with the X axis bounded by START and END."
469
(with-bitmap (width height)
470
(let ((step (/ (- end start) width))
471
(mid (floor height 2)))
472
(loop for x from start to end by step
474
do (let ((y (- mid (floor (funcall fn x) step))))
475
(set-pixel xi (floor y))))
479
(defun rotate-rows-to-columns (rows)
480
(loop for remaining = rows then (mapcar #'cdr remaining)
481
while (not (every #'null remaining))
482
collect (mapcar #'car remaining)))
484
(defun maximize-length (list &key (key #'identity))
485
(loop for element in list maximizing (length (funcall key element))))
487
(defun pad-list (list length &optional (pad-element nil))
490
do (when (null (cdr el))
491
(setf (cdr el) (make-list (- length x) :initial-element pad-element))
494
(defun print-table (rows &key (gap " ") (align :left))
497
with max-row-length = (apply #'max (mapcar #'length rows))
498
with control-string =
501
'string "~{~~~D" (ecase align (:right "@") (:left "")) "A~^" gap "~}~%")
502
(mapcar (lambda (row) (maximize-length row :key #'princ-to-string))
503
(rotate-rows-to-columns rows)))
504
for row in (mapcar (lambda (row) (pad-list row max-row-length "")) rows)
505
do (apply #'format t control-string row))))
507
(defun print-heading (text &key (underline "▀"))
511
(dotimes (i (length text)) (write-string underline))
515
(defun print-in-box (string)
516
(flet ((print-times (count string) (dotimes (x count) (princ string))))
517
(let* ((lines (sb-unicode:lines string))
518
(columns (apply #'max (mapcar #'length lines))))
519
(princ "┌") (print-times columns "─") (princ "┐") (fresh-line)
520
(loop for line in lines
523
(print-times (- columns (length line)) " ")
526
(princ "└") (print-times columns "─") (princ "┘")
529
(defmacro print-boxed (&rest body)
531
(with-output-to-string (*standard-output*)
536
;; APL Box Formatting (BQN/Dyalog/J)
538
(defun draw-one-in-chance (&optional (chance 3))
539
"Show a 32x32 bitmap with pixels on with a one in CHANCE probability."
540
(draw-from-list (random-booleans (* 32 32) chance) 32))
542
(defun draw-chance (&optional (steps 80))
543
"Show a bitmap with each columns pixels with decreasing probability."
544
(draw (make-bitmap steps 32
545
(loop for y from 1 to 32
546
collect (loop for i from 1 to steps
547
collect (>= (random steps) i))))))