;;; -*- Mode: Lisp; Package: SDRAW -*- ;;; ;;; SDRAW - draws cons cell structures. ;;; ;;; From the book "Common Lisp: A Gentle Introduction to ;;; Symbolic Computation" by David S. Touretzky. ;;; The Benjamin/Cummings Publishing Co., 1990. ;;; ;;; This version is for Golden Common Lisp version 1.01 and 1.1. ;;; ;;; ** WARNING ** unlike the other versions of SDRAW, this one has not ;;; yet been revised to include the new circular structures code. ;;; ;;; User-level routines: ;;; (SDRAW obj) - draws obj on the terminal ;;; (SDRAW-LOOP) - puts the user in a read-eval-draw loop ;;; (SCRAWL obj) - interactively crawl around obj ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; The parameters below are in units of characters (horizontal) ;;; and lines (vertical). They apply to all versions of SDRAW, ;;; but their values may change if cons cells are being drawn as ;;; bit maps rather than as character sequences. (defvar *sdraw-display-width* 79.) (defvar *sdraw-display-height* 25) (defvar *sdraw-vertical-cutoff* 22.) (defvar *sdraw-horizontal-atom-cutoff* 79.) (defvar *sdraw-horizontal-cons-cutoff* 65.) ; display-width minus length of a cons & longest message (defvar *etc-string* "etc.") (defvar *circ-string* "circ.") (defvar *etc-spacing* 4.) (defvar *circ-spacing* 5.) (defvar *inter-obj-h-spacing* 3.) (defvar *inter-cons-vspacing* 3.) (defvar *cons-obj-h-arrow-length* 9.) (defvar *inter-cons-v-arrow-length* 3.) (defvar *cons-v-arrow-offset-threshold* 2.) (defvar *cons-v-arrow-offset-value* 1.) (defvar *line-endings* (make-array *sdraw-display-height* :initial-element -2)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; SDRAW and subordinate definitions. (defun sdraw (obj) (dotimes (i (array-length *line-endings*)) (setf (aref *line-endings* i) -2)) (clear-window) (clear-term) (draw-structure (struct1 obj 0 0 nil)) (set-term-pos 0 20.)) (defun struct1 (obj row root-col obj-memory) (cond ((not (consp obj)) (struct-process-atom (format nil "~S" obj) row root-col)) ((member obj obj-memory :test #'eq) (struct-process-circ row root-col)) ((>= row *sdraw-vertical-cutoff*) (struct-process-etc row root-col)) (t (struct-process-cons obj row root-col (cons obj obj-memory))))) (defun struct-process-atom (atom-string row root-col) (let* ((start-col (struct-find-start row root-col)) (end-col (+ start-col (flatsize atom-string)))) (cond ((< end-col *sdraw-horizontal-atom-cutoff*) (struct-record-position row end-col) (list 'atom row start-col atom-string)) (t (struct-process-etc row root-col))))) (defun struct-process-etc (row root-col) (let ((start-col (struct-find-start row root-col))) (struct-record-position row (+ start-col (flatsize *etc-string*) *etc-spacing*)) (list 'msg row start-col *etc-string*))) (defun struct-process-circ (row root-col) (let ((start-col (struct-find-start row root-col))) (struct-record-position row (+ start-col (flatsize *circ-string*) *circ-spacing*)) (list 'msg row start-col *circ-string*))) (defun struct-process-cons (obj row root-col obj-memory) (let* ((cons-start (struct-find-start row root-col)) (car-structure (struct1 (car obj) (+ row *inter-cons-v-arrow-length*) cons-start obj-memory)) (start-col (third car-structure))) (if (>= start-col *sdraw-horizontal-cons-cutoff*) (struct-process-etc row root-col) (list 'cons row start-col car-structure (struct1 (cdr obj) row (+ start-col *cons-obj-h-arrow-length*) obj-memory))))) (defun struct-find-start (row root-col) (max root-col (+ *inter-obj-h-spacing* (aref *line-endings* row)))) (defun struct-record-position (row end-col) (setf (aref *line-endings* row) end-col)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; SDRAW-LOOP and subordinate definitions. (defparameter *sdraw-loop-prompt-string* "S> ") (defun sdraw-loop () "Read-eval-print loop using sdraw to display results." (clear-term) (format t "Type any Lisp expression, or (ABORT) to exit.") (sdl1) (reset-terminal)) (defun sdl1 () (loop (setup-terminal) (format t "~&~A" *sdraw-loop-prompt-string*) (multiple-value-bind (form error) (ignore-errors (read)) (cond (error (display-sdl-error error)) (t (setf +++ ++ ++ + + - - form))) (if (equal form '(abort)) (return)) (multiple-value-bind (result error) (ignore-errors (eval form)) (cond (error (display-sdl-error error)) (t (setf /// // // / / result) (display-sdl-result result))))))) (defun display-sdl-result (result) (let* ((*print-circle* t) (*print-length* nil) (*print-level* nil) (*print-pretty* nil) (full-text (format nil "Result: ~S" result)) (text (if (> (length full-text) *sdraw-display-width*) (string-append (subseq full-text 0 (- *sdraw-display-width* 4)) "...)") full-text))) (sdraw result) (if (consp result) (format t "~%~A~%" text)) (terpri))) (defun display-sdl-error (error) (format t "~A~%~%" error)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; SCRAWL and subordinate definitions. (defparameter *scrawl-prompt-string* "SCRAWL> ") (defvar *scrawl-object* nil) (defvar *scrawl-current-obj*) (defvar *extracting-sequence* nil) (defun scrawl (obj) "Read-eval-print loop to travel through list" (setup-terminal) (clear-window) (setf *scrawl-object* obj) (setf *scrawl-current-obj* obj) (setf *extracting-sequence* nil) (sdraw obj) (display-message "Crawl through list, 'Q' to quit, 'H' for help" 0. 21.) (scrawl1) (reset-terminal) ) (defun scrawl1 () (loop (set-term-pos 5. 22.) (let ((prompt *scrawl-prompt-string*)) (princ prompt) (set-term-pos (+ 5. (length prompt)) 22.)) (let ((command (read-uppercase-char))) (clear-term) (case command (#\A (scrawl-car-cmd)) (#\D (scrawl-cdr-cmd)) (#\B (scrawl-back-up-cmd)) (#\S (scrawl-start-cmd)) (#\H (display-scrawl-help)) (#\Q (return)) (t (display-scrawl-error)))))) (defun scrawl-car-cmd () (cond ((consp *scrawl-current-obj*) (push 'car *extracting-sequence*) (setf *scrawl-current-obj* (car *scrawl-current-obj*)) (display-scrawl-result)) (t (display-message "Can't take CAR or CDR of an atom. Use B to back up." 0. 19.)))) (defun scrawl-cdr-cmd () (cond ((consp *scrawl-current-obj*) (push 'cdr *extracting-sequence*) (setf *scrawl-current-obj* (cdr *scrawl-current-obj*)) (display-scrawl-result)) (t (display-message "Can't take CAR or CDR of an atom. Use B to back up." 0. 19.)))) (defun scrawl-back-up-cmd () (cond (*extracting-sequence* (pop *extracting-sequence*) (setf *scrawl-current-obj* (extract-obj *extracting-sequence* *scrawl-object*)) (display-scrawl-result)) (t (display-message "Already at beginning of object." 0. 19.)))) (defun scrawl-start-cmd () (setf *scrawl-current-obj* *scrawl-object*) (setf *extracting-sequence* nil) (display-scrawl-result)) (defun extract-obj (seq obj) (if (null seq) obj (extract-obj (butlast seq) (funcall (car (last seq)) obj)))) (defun get-car/cdr-string () (if (null *extracting-sequence*) (format nil "'~S" *scrawl-object*) (format nil "(c~Ar '~S)" (apply #'string-append (mapcar #'(lambda (x) (case x (car "a") (cdr "d"))) *extracting-sequence*)) *scrawl-object*))) (defun display-scrawl-result (&aux (*print-pretty* nil) (*print-length* nil) (*print-level* nil) (*print-circle* t)) (let* ((extract-string (get-car/cdr-string)) (text (if (> (length extract-string) *sdraw-display-width*) (string-append (subseq extract-string 0 (- *sdraw-display-width* 4)) "...)") extract-string))) (clear-window) (sdraw *scrawl-current-obj*) (display-message text 0. 18.))) (defun display-scrawl-help () (display-message "Legal commands: A)car D)cdr B)back up" 0. 20.) (display-message " S)start Q)quit H)help" 0. 21.)) (defun display-scrawl-error () (display-message "Illegal command." 0. 19.) (display-scrawl-help)) (defun read-uppercase-char () (let ((in (send *terminal-io* :read-char))) (char-upcase in))) (defun display-message (msg col row) (set-term-pos col (- row 18)) (princ msg)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Everything below this line is specific to the ;;; "Golden Common Lisp" implementation, and is not ;;; referenced directly by SDRAW.LISP. The only exception ;;; is DRAW-STRUCTURE (called by SDRAW). (defvar *cons-string* '(222. 219. 222. 219.)) (defvar *cons-cell-flatsize* 4.) (defvar *cons-h-arrowshaft* 196.) (defvar *cons-h-arrowhead* 62.) (defvar *cons-v-line* 179.) (defvar *cons-v-arrowhead* 245.) (defvar *window* (make-window-stream :left 0 :top 0 :width 80. :height 19.)) (unless (boundp '*monitor-is-color*) ;version 1.01 vs. version 1.1 (setq *monitor-is-color* sys::*monitor-is-color*)) (defconstant normal-att (if *monitor-is-color* #x0f #x07)) (defconstant cons-att (if *monitor-is-color* #x0b normal-att)) (defconstant msg-att (if *monitor-is-color* #x0e normal-att)) (macro with-attribute (form) (let ((x (if (atom (cadr form)) (cadr form) (caadr form))) (strm (or (and (consp (cadr form)) (cdr (cadr form)) (cadr (cadr form))) '*terminal-io*)) (body (cddr form))) (rplacb form `(let ((oldatt (send ,strm :attribute))) (unwind-protect (progn (send ,strm :set-attribute ,x) . ,body) (send ,strm :set-attribute oldatt)))))) (defun setup-terminal () (send *terminal-io* :set-position 0 19.) (send *terminal-io* :set-size 80. 6.) (send *terminal-io* :set-cursorpos 0 0)) (defun reset-terminal () (send *terminal-io* :set-position 0 0) (send *terminal-io* :set-size 80. 25.) (send *terminal-io* :set-cursorpos 79. 24.) (terpri)) (defmacro clear-term () `(send *terminal-io* :clear-screen)) (defmacro set-term-pos (x y) `(send *terminal-io* :set-cursorpos ,x ,y)) (defun write-window (x) (write-byte x *window*)) (defmacro set-window-pos (x y) `(send *window* :set-cursorpos ,x ,y)) (defmacro clear-window () `(send *window* :clear-screen)) (defun draw-structure (directions) (follow-directions directions)) (defun follow-directions (dirs &optional is-car) (case (car dirs) (cons (draw-cons dirs)) ((atom msg) (draw-msg (second dirs) (third dirs) (nth 3 dirs) is-car)))) (defun draw-cons (obj) (let* ((row (second obj)) (col (third obj)) (car-component (nth 3 obj)) (cdr-component (nth 4 obj)) (h-arrow-start (+ col *cons-cell-flatsize*)) (h-arrowhead-col (1- (third cdr-component)))) (set-window-pos col row) (with-attribute (cons-att *window*) (mapc #'write-window *cons-string*) (do ((i h-arrow-start (1+ i))) ((>= i h-arrowhead-col)) (write-window *cons-h-arrowshaft*)) (write-window *cons-h-arrowhead*) (set-window-pos (1+ col) (+ row 1)) (write-window *cons-v-line*) (set-window-pos (1+ col) (+ row 2)) (write-window *cons-v-arrowhead*)) (follow-directions car-component t) (follow-directions cdr-component nil))) (defun draw-msg (row col string is-car) (with-attribute (msg-att *window*) (set-window-pos (+ col (if (and is-car (<= (length string) *cons-v-arrow-offset-threshold*)) *cons-v-arrow-offset-value* 0)) row) (princ string *window*)))