Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
;;; show-conses.el --- Shows cons-cell diagrams -*- lexical-binding: nil; -*- ;; ;; Copyright (C) 2024 Eduardo Ochs ;; ;; Author: Eduardo Ochs <eduardoochs@gmail.com> ;; Maintainer: Eduardo Ochs <eduardoochs@gmail.com> ;; Created: 2024oct20 ;; Modified: 2025feb16 ;; Version: 0.0.20250216 ;; Homepage: http://anggtwu.net/show-conses.html ;; Package-Requires: ((eev "20241223")) ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;; ;;; Commentary: ;; ;; See: ;; http://anggtwu.net/show-conses.html ;; http://anggtwu.net/show-conses/show-conses.el.html ;; http://anggtwu.net/show-conses/show-conses.el ;; (find-showconses "show-conses.el") ;; Git repository: ;; https://github.com/edrx/show-conses ;; ;; EVERYTHING HERE IS VERY NEW & PRELIMINARY! ;; ;; (defun e () (interactive) (find-showconses "show-conses.el")) ;; (defun sc0 () (interactive) (find-angg "show-conses/show-conses-0.el")) ;; (defun sc () (interactive) (find-angg "show-conses/show-conses.el")) ;; (Re)load with: ;; ;; (load (buffer-file-name)) ;; Index: ;; ;; «.intro» (to "intro") ;; «.sc-:do1» (to "sc-:do1") ;; «.makeconcat» (to "makeconcat") ;; «.colors» (to "colors") ;; «.sc-show» (to "sc-show") ;; «.markers» (to "markers") ;; «.overlay» (to "overlay") ;; «.:m1» (to ":m1") ;; «.insert» (to "insert") ;; «.show-regions» (to "show-regions") ;; «.rect-objects» (to "rect-objects") ;; «.:toplain» (to ":toplain") ;; «.insertrects» (to "insertrects") ;; «.hpad-and-vpad» (to "hpad-and-vpad") ;; «.glueing-rects» (to "glueing-rects") ;; ;; «.:expand» (to ":expand") ;; «.:m» (to ":m") ;; «.:sexp» (to ":sexp") ;; «.setoverlay-buttons» (to "setoverlay-buttons") ;; «.typedescr-buttons» (to "typedescr-buttons") ;; «.:sexptree» (to ":sexptree") ;; «.find-show-conses» (to "find-show-conses") ;; «.find-classtree» (to "find-classtree") ;; See: (find-eev "eev-load.el") ;; (find-eev-levels-intro) (require 'eev-load) (require 'cl-extra) ;;; ___ _ ;;; |_ _|_ __ | |_ _ __ ___ ;;; | || '_ \| __| '__/ _ \ ;;; | || | | | |_| | | (_) | ;;; |___|_| |_|\__|_| \___/ ;;; ;; «intro» (to ".intro") ;; Skel: (find-intro-links "show-conses") ;; Test: (find-show-conses-intro) ;;;###autoload (defun find-show-conses-intro (&rest pos-spec-list) (interactive) (let ((ee-buffer-name "*(find-show-conses-intro)*")) (apply 'find-eintro "\ \(Re)generate: (find-show-conses-intro) Source code: (find-efunction 'find-show-conses-intro) More intros: (find-eev-quick-intro) (find-eev-intro) This buffer is _temporary_ and _editable_. It is meant as both a tutorial and a sandbox. Prerequisites: (find-eev-quick-intro \"2. Evaluating Lisp\") (find-eev-quick-intro \"3. Elisp hyperlinks\") 1. Introduction =============== This is a \"cons diagram\", (1 (2 \"3\") . 4) .__._______4 | | 1 .__. | | 2 \"3\" that shows a sexp, (1 (2 \"3\") . 4), and how that sexp is represented using conses. It uses a format that is much more compact than the formats used by pair-tree.el - that is an Emacs package that is MELPA - and by \"Sdraw\" from Racket. Compare: https://github.com/zainab-ali/pair-tree.el https://docs.racket-lang.org/sdraw/index.html If you are reading this in Emacs then you have already loaded \"show-conses.el\", that is able to display such diagrams. Try: (find-show-conses-2a '(1 (2 \"3\") . 4)) You will get a two-window setting like this, _________________________ | | | | | | | intro | *show-conses* | | | | |_________|_______________| and in the \"*show-conses*\" buffer some parts of the cons tree are \"highlighters\". For example, if you go to the \".\" that corresponds to the sub-sexp (2 \"3\") and type `C-c C-c' there it will highlight the `(2 \"3\")' in the upper part. Try that now. The highlighters are implemented using text properties, and the regions that they highlight are implemented using a hash table that associates names to markers. Try: [TODO: rewrite everything below this point!!!] (find-epp (show-conses-propertize-h '(h \".\" \"cadr\"))) (show-conses-delete-markers) (find-show-conses-lisp-3a '(1 (2 \"3\") . 4) :end) (show-conses-set-overlay-1 \"car\") (show-conses-set-overlay-1 \"cr\") (show-conses-delete-overlay) (find-2a nil '(find-ehashtable show-conses-markers)) 2. Intended audience ==================== You can use sexps like (find-show-conses-lisp-2a '(1 (2 \"3\") . 4) :end) to explain sexps and conses to your friends, but I consider that this package is: \"...more like a toy that is _slightly interesting_ if you play with it for a few seconds, and _much more interesting_ if you open it and take its pieces apart to see how everything works.\" For more on that, see: http://anggtwu.net/2024-eev-for-5-year-olds.html#taking-apart 3. Namespaces ============= The file \"show-conses.el\" is well-behaved: it only defines symbols that start with \"show-conses-\" or \"find-show-conses\", plus a few extensions to `M-e', that start with \"ee-\" and that, ahem, \"invade the eev namespace\". Check: (find-eloadhistory-for 'show-conses-shorten) (find-eloadhistory-for 'ee-eval-last-sexp) Remember that _most_ short and cryptic names, like `a' and `foo' - but not `t', `car', and `pi' - are reserved for users. The function `show-conses-export' (sort of) exports the symbols of show-conses.el halfway towards this \"user namespace\", by creating shorter aliases in which each \"show-conses\" is replaced by just \"sc\". You can inspect what `show-conses-export' does by running this: (find-estring-elisp (show-conses-export0)) Run this to define these shorter symbols: ;; See: (find-efunction 'show-conses-export) (show-conses-export) I will use the shorter symbols in most of the examples of this intro. 4. The DSL ========== " pos-spec-list))) ;; (defun e () (interactive) (find-angg "elisp/show-conses.el")) ;; (find-show-conses-intro) ;;; _ _ ;;; ___ ___ _ __| | ___ / | ;;; / __|/ __|___(_) _` |/ _ \| | ;;; \__ \ (_|_____| (_| | (_) | | ;;; |___/\___| (_)__,_|\___/|_| ;;; ;; «sc-:do1» (to ".sc-:do1") ;; (sc-:do1 "foo") ;; (sc-:do1 123456) ;; (sc-:do1 'asymbol) ;; (sc-:do1 '(:do1 "foo")) ;; (sc-:do1 '(:bar "foo")) ;; (sc-:do1 '(plic "foo")) ;; (defun sc-:do1 (o) (cond ((stringp o) o) ((keywordp (car-safe o)) (apply (ee-intern "sc-%s" (car o)) (cdr o))) (t (error "sc-:do1 is confused by: %S" o)))) ;; (insert "\n" (sc-:face1 'warning "foo")) ;; (insert "\n" (sc-:fg1 "red" "foo")) ;; (insert "\n" (sc-:fg1 'red "aaa") (sc-:fg1 'yellow "bbb")) (defun sc-:face1 (face str) (propertize str 'font-lock-face face)) (defun sc-:face1 (face str) (propertize str 'font-lock-face face 'face face)) (defun sc-:fg1 (color str) (sc-:face1 `(:foreground ,(format "%s" color)) str)) ;;; __ __ _ _ ;;; | \/ | __ _| | _____ ___ ___ _ __ ___ __ _| |_ ;;; | |\/| |/ _` | |/ / _ \/ __/ _ \| '_ \ / __/ _` | __| ;;; | | | | (_| | < __/ (_| (_) | | | | (_| (_| | |_ ;;; |_| |_|\__,_|_|\_\___|\___\___/|_| |_|\___\__,_|\__| ;;; ;; «makeconcat» (to ".makeconcat") ;; (defmacro sc-makeconcat (keyword arglist stringcmd) `(defun ,(ee-intern "sc-%s" keyword) (,@arglist &rest objs) (cl-loop for o in objs concat (if (stringp o) ,stringcmd (sc-:do1 o))))) ;; To understand `sc-makeconcat', ;; compare: ' (find-2a nil '(find-eppm '(sc-makeconcat :fg (color) (sc-:fg1 color o)))) ;; with: ' (defun sc-:fg (color &rest objs) (cl-loop for o in objs concat (if (stringp o) (sc-:fg1 color o) (sc-:do1 o)))) ;;; ____ _ ;;; / ___|___ | | ___ _ __ ___ ;;; | | / _ \| |/ _ \| '__/ __| ;;; | |__| (_) | | (_) | | \__ \ ;;; \____\___/|_|\___/|_| |___/ ;;; ;; «colors» (to ".colors") ;; To understand the words made by `sc-makeconcat', try: ;; ;; (insert "\n" (sc-:do1 '(:fg green "c" "d"))) ;; (insert "\n" (sc-:do1 '(:green "c" "d"))) ;; (insert "\n" (sc-:do1 '(:yellow "b" (:green "c" "d") "e"))) ;; (insert "\n" (sc-:do1 '(:red "a" (:yellow "b" (:green "c" "d") "e") "f"))) ;; (insert "\n" (sc-:do1 '(:red "a" (:yellow "b" (:concat "c" "d") "e") "f"))) ;; (sc-makeconcat :concat () o) (sc-makeconcat :face (face) (sc-:face1 face o)) (sc-makeconcat :fg (color) (sc-:fg1 color o)) (sc-makeconcat :red () (sc-:fg1 'red o)) (sc-makeconcat :blue () (sc-:fg1 'blue o)) (sc-makeconcat :green () (sc-:fg1 'green o)) (sc-makeconcat :yellow () (sc-:fg1 'yellow o)) ;;; _ ;;; ___ ___ ___| |__ _____ __ ;;; / __|/ __|____/ __| '_ \ / _ \ \ /\ / / ;;; \__ \ (_|_____\__ \ | | | (_) \ V V / ;;; |___/\___| |___/_| |_|\___/ \_/\_/ ;;; ;; «sc-show» (to ".sc-show") (defun sc-last-sexp () (ee-eval-last-sexp-0) ; highlight (read (ee-last-sexp))) (defun sc-show (sexp) (interactive (list (sc-last-sexp))) (find-2a nil `(find-estring (sc-:do1 ',sexp) :end))) (defun sc-message (sexp) (interactive (list (sc-last-sexp))) (message "%s" (sc-:do1 sexp))) (defalias 'scs 'sc-show) (defalias 'scm 'sc-message) ;; Try `M-x scs' and `M-x scm' at various points of: ;; (:red "a" (:yellow "b" (:concat "c" "d") "e") "f") ;; (find-showconses "show-conses.el" "markers") ;;; __ __ _ ;;; | \/ | __ _ _ __| | _____ _ __ ___ ;;; | |\/| |/ _` | '__| |/ / _ \ '__/ __| ;;; | | | | (_| | | | < __/ | \__ \ ;;; |_| |_|\__,_|_| |_|\_\___|_| |___/ ;;; ;; «markers» (to ".markers") ;; Test: ;; (sc-:deleteallmarkers) ;; (sc-:setmarker "a") ;; (sc-:setmarker "b") ;; (find-ehashtable sc-markers) ;; (sc-:getmarker "a") ;; (sc-:getmarker "err") ;; (defvar sc-markers (make-hash-table :test 'equal)) (defun sc-:deleteallmarkers () (setq sc-markers (make-hash-table :test 'equal))) (defun sc-:deletemarker (name) (remhash name sc-markers)) (defun sc-:getmarker (name) (or (gethash name sc-markers) (error (format "Failed: (get-hash %S sc-markers)" name)))) (defun sc-:setmarker (name &optional pos buffer) (with-current-buffer (or buffer (current-buffer)) (if pos (goto-char pos)) (puthash name (point-marker) sc-markers))) ;;; ___ _ ;;; / _ \__ _____ _ __| | __ _ _ _ ;;; | | | \ \ / / _ \ '__| |/ _` | | | | ;;; | |_| |\ V / __/ | | | (_| | |_| | ;;; \___/ \_/ \___|_| |_|\__,_|\__, | ;;; |___/ ;; «overlay» (to ".overlay") ;; We use a single overlay to highlight parts of a sexp - ;; and we store it in the variable `sc-overlay'. Tests: ;; (sc-:deleteoverlay) ;; (sc-:setoverlay3 (+ 2 (ee-bol)) (+ 4 (ee-bol))) ;; (sc-:setoverlay3 (+ 5 (ee-bol)) (+ 9 (ee-bol))) ;; (sc-:deleteoverlay) ;; (sc-:setmarker "a") ;; (sc-:setmarker "b") ;; (sc-:setmarker "c") ;; (sc-:setoverlay2 "a" "b") ;; (sc-:setoverlay2 "a" "c") ;; (sc-:setoverlay2 "b" "c") ;; (sc-:deleteoverlay) ;; (sc-:setmarker "foo beg") ;; (sc-:setmarker "foo end") ;; (sc-:setoverlay1 "foo") ;; (sc-:deleteoverlay) ;; (defvar sc-overlay nil) (defun sc-:deleteoverlay () "Delete the overlay `sc-overlay' if it exists." (if (overlayp sc-overlay) (delete-overlay sc-overlay))) (defun sc-:setoverlay3 (start end &optional buffer) "A low-level function called by `sc-:setoverlay2'." (setq buffer (or buffer (current-buffer))) (sc-:deleteoverlay) (setq sc-overlay (with-current-buffer buffer (make-overlay start end))) (overlay-put sc-overlay 'face 'highlight)) (defun sc-:setoverlay2 (start end) "Make `sc-:setoverlay3' highlight the region between START and END. START and END should be strings and should be the names of two markers in `sc-markers' that point to the same buffer." (let* ((startmarker (sc-:getmarker start)) (startpos (marker-position startmarker)) (startbuf (marker-buffer startmarker)) (endmarker (sc-:getmarker end)) (endpos (marker-position endmarker)) (endbuf (marker-buffer endmarker))) (if (not (eq startbuf endbuf)) (error (format "Markers %S and %S are in different buffers!" start end))) (sc-:setoverlay3 startpos endpos startbuf))) (defun sc-:setoverlay1 (stem) "Like `sc-:setoverlay2', but receives a single argument." (sc-:setoverlay2 (format "%s beg" stem) (format "%s end" stem)) "") ;;; _ ;;; _ _ __ ___ / | ;;; (_) '_ ` _ \| | ;;; _| | | | | | | ;;; (_)_| |_| |_|_| ;;; ;; «:m1» (to ".:m1") (defvar sc-:m1 'setmarkers "When the variable `sc-:m1' is nil the function `sc-:m1' doesn't run `sc-:setmarker'.") (defun sc-:m1 (name) (if sc-:m1 (sc-:setmarker (format "%s" name))) ; optional "") ; always returns "" (defun sc-:concat-nom (&rest rest) "Like `:concat', but \"with no markers\". This function doesn't run the `sc-:setmarker's in the `sc-:m1's." (let ((sc-:m1 nil)) (apply 'sc-:concat rest))) ;;; ___ _ ;;; |_ _|_ __ ___ ___ _ __| |_ ;;; | || '_ \/ __|/ _ \ '__| __| ;;; | || | | \__ \ __/ | | |_ ;;; |___|_| |_|___/\___|_| \__| ;;; ;; «insert» (to ".insert") (defun sc-:insert (&rest objs) (cl-loop for o in objs do (insert (sc-:do1 o))) "") ;; Test: ;; (sc-:do1 '(:insert "\n;; " (:m1 abL) "ab" (:m1 abR) (:m1 cdL) "cd" (:m1 cdR))) ;; (sc-:setoverlay2 "abL" "abR") ;; (sc-:setoverlay2 "cdL" "cdR") ;; (sc-:deleteoverlay) ;;; ____ _ _ ;;; / ___|| |__ _____ __ _ __ ___ __ _(_) ___ _ __ ___ ;;; \___ \| '_ \ / _ \ \ /\ / / | '__/ _ \/ _` | |/ _ \| '_ \/ __| ;;; ___) | | | | (_) \ V V / | | | __/ (_| | | (_) | | | \__ \ ;;; |____/|_| |_|\___/ \_/\_/ |_| \___|\__, |_|\___/|_| |_|___/ ;;; |___/ ;; ;; «show-regions» (to ".show-regions") ;; Test: ;; (find-2a nil '(find-ehashtable sc-markers)) ;; (sc-:deleteallmarkers) ;; (find-2a nil '(find-ehashtable sc-markers)) ;; (sc-:setmarker "foo beg") ;; (sc-:setmarker "foo end") ;; (sc-:setmarker "bar beg") ;; (sc-:setmarker "bar end") ;; (find-2a nil '(find-showregions)) ;; (defun sc-region-names () "Return all the names of sc-regions, sorted." (let* ((stems (cl-loop for k being the hash-keys of sc-markers if (string-match " beg$" k) collect (replace-regexp-in-string " beg$" "" k)))) (sort stems 'string<))) (defun find-showregions () "Show a temporary buffer with commands to highlight all the sc-regions." (let* ((names (sc-region-names)) (sexps (cl-loop for name in names collect `(sc-:setoverlay1 ,name)))) (find-elinks-elisp `((find-sc-regions) (find-efunction 'find-sc-regions) "" ,(ee-ppp00 sexps) "(sc-:deleteoverlay)" )))) ;;; ____ _ _ _ _ ;;; | _ \ ___ ___| |_ ___ | |__ (_) ___ ___| |_ ___ ;;; | |_) / _ \/ __| __| / _ \| '_ \| |/ _ \/ __| __/ __| ;;; | _ < __/ (__| |_ | (_) | |_) | | __/ (__| |_\__ \ ;;; |_| \_\___|\___|\__| \___/|_.__// |\___|\___|\__|___/ ;;; |__/ ;; ;; «rect-objects» (to ".rect-objects") ;; A `:line' object is a list that can be concatenated by `sc-:concat'. ;; A `:lines' object is a list of `:line's. ;; A `:rect' object is a `:lines' preceded by the symbol `:rect'. ;; In this example, ;; ;; (setq o1 '("a" "b")) ;; (setq o2 '("a" (:red "b" "c") "d")) ;; (setq o3 (sc-:expand '("a" (:@m "bc" "b" "c") "d"))) ;; (setq o4 `(,o1 ,o2 ,o3)) ;; (setq o5 `(:rect ,o1 ,o2 ,o3)) ;; ;; `o1', `o2' and `o3' are `:line's, `o4' is a `:lines', and `o5' is a ;; `:rect'. ;; ;; A `:preline' object is either a string or a `:line' object. ;; A `:prerect' object is either a string or a `:line' or a `:rect'. ;; The function `sc-:toline' converts a `:preline' to a `:line'. ;; The function `sc-:torect' converts a `:prerect' to a `:rect'. ;; The function `sc-:tolines' converts a `:prerect' to a `:lines'. ;; Try: ;; ;; (sc-:torect '(:rect ("a" "b") ("c" "d"))) ;; (sc-:tolines (sc-:torect '(:rect ("a" "b") ("c" "d")))) ;; (sc-:torect '("a" "b")) ;; (sc-:tolines (sc-:torect '("a" "b"))) ;; (sc-:torect "a") ;; (sc-:tolines (sc-:torect "a")) ;; (defun sc-:rectp (o) (if (eq (car-safe o) ':rect) t)) (defun sc-:toline (o) (if (atom o) (list o) o)) (defun sc-:torect (o) (if (sc-:rectp o) o `(:rect ,(sc-:toline o)))) (defun sc-:tolines (o) (cdr (sc-:torect o))) ;;; _ _ _ ;;; _| |_ ___ _ __ | | __ _(_)_ __ ;;; (_) __/ _ \| '_ \| |/ _` | | '_ \ ;;; _| || (_) | |_) | | (_| | | | | | ;;; (_)\__\___/| .__/|_|\__,_|_|_| |_| ;;; |_| ;; ;; «:toplain» (to ".:toplain") ;; The functions `sc-*-line', below are very strict in whay they ;; accept - they only accept `:line's, and they fail when o is a ;; string. ;; (defun sc-:totext-line (o) (let ((sc-:m1 nil)) (apply 'sc-:concat o))) (defun sc-:totext-line (o) (apply 'sc-:concat-nom o)) (defun sc-:toplain-line (o) (ee-no-properties (sc-:totext-line o))) (defun sc-:width-line (o) (length (sc-:toplain-line o))) ;; The functions below are variants of the `sc-*-line's above that are ;; very lenient in what they accept - they accept a `:prerect' and ;; they convert it internally to a `:rect'. ;; (defun sc-:totext (o) (let* ((lines1 (sc-:tolines o)) (lines2 (cl-loop for line in lines1 collect (sc-:totext-line line)))) (mapconcat 'identity lines2 "\n"))) (defun sc-:toplain (o) (let* ((lines1 (sc-:tolines o)) (lines2 (cl-loop for line in lines1 collect (sc-:toplain-line line)))) (mapconcat 'identity lines2 "\n"))) (defun sc-:width (o) (let* ((lines (sc-:tolines o)) (widths (mapcar 'sc-:width-line lines))) (apply 'max widths))) ;; The function `sc-:pile' below accepts a list of `:prerects' ;; and returns a `:rect'. Try: ;; (sc-:pile "a" '("b" "c") '(:rect ("d") ("e" "f"))) ;; (sc-:toplain (sc-:pile "a" '("b" "c") '(:rect ("d") ("e" "f")))) ;; (sc-:pile "a") ;; (sc-:pile (sc-:pile "a")) ;; (defun sc-:pile (&rest objs) (let* ((liness (mapcar 'sc-:tolines objs)) (lines (apply 'append liness))) `(:rect ,@lines))) ;;; ___ _ _ ;;; |_ _|_ __ ___ ___ _ __| |_ _ __ ___ ___| |_ ___ ;;; | || '_ \/ __|/ _ \ '__| __| '__/ _ \/ __| __/ __| ;;; | || | | \__ \ __/ | | |_| | | __/ (__| |_\__ \ ;;; |___|_| |_|___/\___|_| \__|_| \___|\___|\__|___/ ;;; ;; «insertrects» (to ".insertrects") ;; See: (to "insert") ;; Tests: ;; (sc-:insertrects "" "a" '(:rect ("b" "c") ("d"))) ;; (find-insertrects "" "a" '(:rect ("b" "c") ("d"))) ;; (find-insertrects-2a "a" '(:rect ("b" "c") ("d"))) ;; (find-insertrects-2a '((:m1 abL) "ab" (:m1 abR) (:m1 cdL) "cd" (:m1 cdR))) ;; (sc-:setoverlay2 "abL" "abR") ;; (sc-:setoverlay2 "cdL" "cdR") ;; (sc-:deleteoverlay) ;; (find-insertrects-3a '("a" (:m1 "bc beg") "bc" (:m1 "bc end") "d")) ;; (sc-:setoverlay1 "bc") ;; (sc-:deleteoverlay) ;; (defun sc-:insertrects (&rest objs) (dolist (o objs) (cl-loop for line in (sc-:tolines o) do (progn (apply 'sc-:insert line) (insert "\n"))))) (defun find-insertrects (&rest objs) (sc-:deleteallmarkers) (find-eoutput-rerun (or ee-buffer-name "*show-conses*") `(apply 'sc-:insertrects ',objs) :end)) (defun find-insertrects-2a (&rest objs) (find-2a nil `(apply 'find-insertrects ',objs))) (defun find-insertrects-3a (&rest objs) (find-3a nil `(apply 'find-insertrects ',objs) '(find-showregions))) ;;; _ _ _ _ _ ;;; | | | |_ __ __ _ __| | __ _ _ __ __| | __ ___ __ __ _ __| | ;;; | |_| | '_ \ / _` |/ _` | / _` | '_ \ / _` | \ \ / / '_ \ / _` |/ _` | ;;; | _ | |_) | (_| | (_| | | (_| | | | | (_| | \ V /| |_) | (_| | (_| | ;;; |_| |_| .__/ \__,_|\__,_| \__,_|_| |_|\__,_| \_/ | .__/ \__,_|\__,_| ;;; |_| |_| ;; ;; «hpad-and-vpad» (to ".hpad-and-vpad") ;; Tests: ;; (sc-:hpad '(:rect ("a") ("b" "c")) 5 ?_) ;; (sc-:vpad '(:rect ("a") ("b" "c")) 5) ;; (defun sc-:hpad-line (o newwidth &optional char) (let* ((oldwidth (sc-:width-line o)) (nchars (max 0 (- newwidth oldwidth))) (newchars (make-string nchars (or char 32)))) `(,@o ,newchars))) (defun sc-:hpad (o newwidth &optional char) (let* ((lines1 (sc-:tolines o)) (lines2 (cl-loop for line in lines1 collect (sc-:hpad-line line newwidth char)))) `(:rect ,@lines2))) (defun sc-:height (o) (length (sc-:tolines o))) (defun sc-:vpad (o newheight) (let* ((lines1 (sc-:tolines o)) (height1 (length lines1)) (nblanks (max 0 (- newheight height1))) (blanks (cl-loop for i from 1 to nblanks collect '("")))) `(:rect ,@lines1 ,@blanks))) ;;; ____ _ _ _ ;;; / ___| |_ _ ___(_)_ __ __ _ _ __ ___ ___| |_ ___ ;;; | | _| | | | |/ _ \ | '_ \ / _` | | '__/ _ \/ __| __/ __| ;;; | |_| | | |_| | __/ | | | | (_| | | | | __/ (__| |_\__ \ ;;; \____|_|\__,_|\___|_|_| |_|\__, | |_| \___|\___|\__|___/ ;;; |___/ ;; ;; «glueing-rects» (to ".glueing-rects") ;; Tests: ;; (setq o1 '(:rect ("a") ("|") ("bcd"))) ;; (setq o2 (sc-:addwire o1)) ;; (setq o3 '(:rect ("e") ("|") ("f"))) ;; (setq o4 (sc-:glue o2 o3)) ;; (find-insertrects-2a o1 "" o2 "" o3 "" o4) ;; (find-insertrects-2a o1 "" o3 "" (sc-:glue o1 "__" o3)) ;; (find-insertrects-2a o1 "" o3 "" (sc-:gluewithwires o1 o3)) ;; (defun sc-:glue2 (a b) (let* ((newheight (max (sc-:height a) (sc-:height b)))) (setq a (sc-:vpad a newheight)) (setq b (sc-:vpad b newheight)) (setq a (sc-:hpad a (sc-:width a))) (let ((clines (cl-loop for aline in (sc-:tolines a) for bline in (sc-:tolines b) collect `(,@aline ,@bline)))) `(:rect ,@clines)))) (defun sc-:glue (&rest objs) (if (< (length objs) 2) (car objs) (sc-:glue2 (car objs) (apply 'sc-:glue (cdr objs))))) (defun sc-:addwire (o) (let* ((lines (sc-:tolines o)) (topline (car lines)) (otherlines (cdr lines)) (newwidth (+ (sc-:width o) 2)) (newtopline (sc-:hpad-line topline newwidth ?_))) `(:rect ,newtopline ,@otherlines))) (defun sc-:gluewithwires (&rest list) (if (null list) (error)) (let* ((revlist (reverse list)) (last (car revlist)) (revrest (cdr revlist)) (result (sc-:torect last))) (dolist (o revrest) (setq result (sc-:glue (sc-:addwire o) result))) result)) ;; Test: ;; (find-insertrects-2a (sc-:rtree "abc" (sc-:rtree "d" "e" "f") "ghi")) (defun sc-:rtree (root &rest subtrees) (if (null subtrees) root (sc-:glue root " " (apply 'sc-:pile subtrees)))) ;;; _____ _ ;;; | ____|_ ___ __ __ _ _ __ __| | ;;; | _| \ \/ / '_ \ / _` | '_ \ / _` | ;;; | |___ > <| |_) | (_| | | | | (_| | ;;; |_____/_/\_\ .__/ \__,_|_| |_|\__,_| ;;; |_| ;; ;; «:expand» (to ".:expand") ;; `sc-:expand' implements another way to expand expressions ;; containing `:'-keywords; `sc-:expand' is much harder to ;; implement than `sc-:do1', so we only introduce it now. ;; ;; Let's define a function `sc-:<>' for tests: (defun sc-:<> (&rest body) `(< ,@body >)) ;; ;; Compare: ;; (sc-:expand '(1 (:<> 2 3) 4 (:@<> 5 6) 7)) ;; `(1 ,(sc-:<> 2 3) 4 ,@(sc-:<> 5 6) 7) ;; they both return this: ;; '(1 (< 2 3 >) 4 < 5 6 > 7) ;; ;; ...so the `:<>' is "expanded normally", ;; and the `:@<>' is "expanded with splicing". ;; Note that `:<>' and `:@<>' do not expand their arguments: ;; ;; (sc-:expand '(1 (:@<> 2 (:@<> 3 4) 3) 5)) ;; --> '(1 < 2 (:@<> 3 4) 3 > 5) ;; ;; We will only define an expander that expands its arguments at the ;; end of this section - look for `:@<e>'. ;; Tests: ;; (sc-:symbol :<>) ;; --> sc-:<> ;; (sc-:@symbol :@<>) ;; --> sc-:<> ;; (defun sc-:symbol (symbol) "If SYMBOL is `:foo' and `sc-:foo' is fbound, then return `sc-:foo'." (if (keywordp symbol) (let ((symbol2 (intern-soft (format "sc-%s" symbol)))) (if (fboundp symbol2) symbol2)))) (defun sc-:@symbol (symbol) "If SYMBOL is `:@foo' and `sc-:foo' is fbound, then return `sc-:foo'." (if (keywordp symbol) (if (string-match "^:@" (symbol-name symbol)) (let* ((stem (substring (symbol-name symbol) 2)) (symbol2 (intern-soft (format "sc-:%s" stem)))) symbol2)))) ;; Test: ;; (sc-:expand-:list '(:<> 2 3)) ;; (sc-:expand-:@list '(:@<> 4 5)) ;; (sc-:expand-plainlist '(1 (2 3) (:<> 4 5) (:@<> 6 7) 8)) ;; (sc-:expand '(1 (2 3) (:<> 4 5) (:@<> 6 7) 8)) ;; --> '(1 (2 3) (< 4 5 >) < 6 7 > 8) ;; (defun sc-:expand-plainlist (list) (cl-loop for o in list append (sc-:expand-inner o))) (defun sc-:expand-:list (list) (apply (sc-:symbol (car list)) (cdr list))) (defun sc-:expand-:@list (list) (apply (sc-:@symbol (car list)) (cdr list))) (defun sc-:expand-inner (o) (cond ((atom o) (list o)) ((sc-:symbol (car o)) (list (sc-:expand-:list o))) ((sc-:@symbol (car o)) (sc-:expand-:@list o)) (t (list (sc-:expand-plainlist o))))) (defun sc-:expand (o) (cond ((atom o) o) ((sc-:symbol (car o)) (sc-:expand-:list o)) ((sc-:@symbol (car o)) (sc-:expand-:@list o)) (t (sc-:expand-plainlist o)))) (defun sc-:expand2 (o) (sc-:expand (sc-:expand o))) ;; Remember that `sc-:<>' _quotes_ its arguments... ;; The function `sc-:<e>' below _expands_ its arguments. ;; Test: ;; (sc-:expand '(1 (:@<e> (:@<e> 2 3)) (:@<> (:@<> 4 5)) 6)) ;; --> '(1 < < 2 3 > > < (:@<> 4 5) > 6) ;; (defun sc-:<e> (&rest body) `(< ,@(sc-:expand-plainlist body) >)) ;;; ;;; _ _ __ ___ ;;; (_) '_ ` _ \ ;;; _| | | | | | ;;; (_)_| |_| |_| ;;; ;; «:m» (to ".:m") ;; `:m' is a macro that adds `beg' and `end' markers - or, ;; in debugging mode, adds "<tag: " and ">". For example: ;; ;; (sc-:expand '(:m "ab" "a" "b")) ;; --> '((:m1 "ab beg") "a" "b" (:m1 "ab end")) ;; (sc-:expand<> '(:m "ab" "a" "b") ;; --> '("<ab: " "a" "b" ">") ;; (sc-:expand<>c '(:m "ab" "a" "b")) ;; --> "<ab: ab>" ;; ;; A high-level test: ;; ;; (setq o '("a" (:@m "bcde" "b" (:@m "cd" "c" "d") "e") "f")) ;; (sc-:expand o) ;; (find-insertrects-3a (sc-:expand o)) ;; (sc-:setoverlay1 "bcde") ;; (sc-:setoverlay1 "cd") ;; (sc-:deleteoverlay) ;; (defvar sc-:m 'sc-:m-:m1) (defun sc-:m (&rest rest) (apply sc-:m rest)) (defun sc-:m-:m1 (tag &rest body) (let* ((tagbeg (format "%s beg" tag)) (tagend (format "%s end" tag))) `((:m1 ,tagbeg) ,@(sc-:expand-plainlist body) (:m1 ,tagend)))) (defun sc-:m-<> (tag &rest body) `(,(format "<%s: " tag) ,@(sc-:expand-plainlist body) ">")) (defun sc-:expand<> (o) (let ((sc-:m 'sc-:m-<>)) (sc-:expand o))) (defun sc-:expand<>c (o) (apply 'concat (sc-:expand<> o))) ;;; ;;; _ ___ _____ ___ __ ;;; (_) __|/ _ \ \/ / '_ \ ;;; _\__ \ __/> <| |_) | ;;; (_)___/\___/_/\_\ .__/ ;;; |_| ;; ;; «:sexp» (to ".:sexp") ;; Tests - choose one of the `setqs', then run the rest: ;; (setq o (sc-:sexp 42)) ;; (setq o (sc-:sexp '(2 . 3))) ;; (setq o (sc-:sexp '(2 3))) ;; (setq o (sc-:sexp '(2 3 4))) ;; (setq o (sc-:sexp '(2 3 . 4))) ;; (setq o (sc-:sexp '(2))) ;; (setq o (sc-:sexp '((2)))) ;; (setq o (sc-:sexp '((2) 3 (4 5) . 6))) ;; (sc-:expand o) ;; (sc-:expand2 o) ;; (find-insertrects-3a (sc-:expand2 o)) (defvar sc-context "o") (defun sc-:m-sub (fmt &rest body) (let ((sc-context (format fmt sc-context))) `(:@m ,sc-context ,@(sc-:expand-plainlist body)))) (defun sc-:m-c (&rest body) (apply 'sc-:m-sub "%s" body)) (defun sc-:m-car (&rest body) (apply 'sc-:m-sub "%s.car" body)) (defun sc-:m-cdr (&rest body) (apply 'sc-:m-sub "%s.cdr" body)) (defun sc-:cons0 (p o) (if (null (cdr o)) `(,p (:m-car ,@(sc-:sexp0 (car o))) ")") (if (atom (cdr o)) `(,p (:m-car ,@(sc-:sexp0 (car o))) " . " (:m-cdr ,@(sc-:sexp0 (cdr o))) ")") `(,p (:m-car ,@(sc-:sexp0 (car o))) (:m-cdr ,@(sc-:cons0 " " (cdr o))))))) (defun sc-:sexp0 (o) (if (atom o) (list (ee-S o)) (sc-:cons0 "(" o))) (defun sc-:sexp (o) `(:m-c ,@(sc-:sexp0 o))) ;;; ____ _ _ ;;; / ___| ___| |_ _____ _____ _ __| | __ _ _ _ ;;; \___ \ / _ \ __/ _ \ \ / / _ \ '__| |/ _` | | | | ;;; ___) | __/ || (_) \ V / __/ | | | (_| | |_| | ;;; |____/ \___|\__\___/ \_/ \___|_| |_|\__,_|\__, | ;;; |___/ ;; ;; «setoverlay-buttons» (to ".setoverlay-buttons") ;; See: (to "overlay") ;; When we draw a cons cell diagram like this one, ;; ;; (2 3) ;; .__. ;; | | ;; 2 3 ;; ;; each node of the lower half gets text properties that make it ;; highlight a part of the upper half when we press `C-c C-c' on the ;; node. These text properties are similar to how Emacs implements ;; buttons, but are much simpler - and I will say that node in the ;; lower half becomes a "setoverlay button". ;; ;; Note that `sc-:hl1' is similar to `sc-:face1' and `sc:fg1', that ;; are defined here: ;; (to "sc-:do1") (defun sc-setoverlay-action (tag) (interactive (list (get-text-property (point) 'tag))) (message "Set overlay %s" tag) (sc-:setoverlay1 tag)) (defvar sc-setoverlay-keymap (make-sparse-keymap)) (define-key sc-setoverlay-keymap (kbd "C-c C-c") 'sc-setoverlay-action) (defvar sc-setoverlay-category "See `sc-:hl1'.") (setplist 'sc-setoverlay-category `(mouse-face highlight face (:foreground "red") font-lock-face (:foreground "red") keymap ,sc-setoverlay-keymap)) (defun sc-:hl1 (tag &optional str) (propertize (or str tag) 'tag tag 'category 'sc-setoverlay-category 'face '(:foreground "red") 'font-lock-face '(:foreground "red") 'help-echo (format "Set overlay %s" tag) ; broken )) ;; (setq o (sc-:sexp '(2 . 3))) ;; (sc-:deleteallmarkers) ;; (apply 'sc-:insert "\n;; --> " (sc-:expand (sc-:expand o))) ;; --> (2 . 3) ;; (find-2a nil '(find-showregions)) ;; (insert "\n;; <" (sc-:hl1 "o") "><" (sc-:hl1 "o.car") ">") ;; <o><o.car> ;; (find-es "emacs" "propertize") ;;; _____ _ ;;; |_ _| _ _ __ ___ __| | ___ ___ ___ _ __ ;;; | || | | | '_ \ / _ \/ _` |/ _ \/ __|/ __| '__| ;;; | || |_| | |_) | __/ (_| | __/\__ \ (__| | ;;; |_| \__, | .__/ \___|\__,_|\___||___/\___|_| ;;; |___/|_| ;; ;; «typedescr-buttons» (to ".typedescr-buttons") (defun sc-typedescr-action (tag) (interactive (list (get-text-property (point) 'tag))) (message "Run (find-etypedescr '%s)" tag) (find-2a nil `(find-etypedescr ',tag))) (defvar sc-typedescr-keymap (make-sparse-keymap)) (define-key sc-typedescr-keymap (kbd "C-c C-c") 'sc-typedescr-action) (defvar sc-typedescr-category "") (setplist 'sc-typedescr-category `(face (:foreground "red") font-lock-face (:foreground "red") mouse-face highlight keymap ,sc-typedescr-keymap)) (defun sc-:td1 (tag &optional str) (propertize (or str (format "%s" tag)) 'tag tag 'category 'sc-typedescr-category 'help-echo (format "Run (find-etypedescr '%s)" tag) ; broken )) ;;; ____ _ ;;; / ___| _____ ___ __ | |_ _ __ ___ ___ ;;; \___ \ / _ \ \/ / '_ \| __| '__/ _ \/ _ \ ;;; ___) | __/> <| |_) | |_| | | __/ __/ ;;; |____/ \___/_/\_\ .__/ \__|_| \___|\___| ;;; |_| ;; ;; «:sexptree» (to ".:sexptree") ;; Compare with: (to ":sexp") ;; Tests: ;; (setq o1 '((2) 3 (4 5) . 6)) ;; (sc-:toplain (sc-:sexptree o1)) ;; (sc-:toplain (sc-:sexp-and-tree o1)) ;; (find-insertrects-2a (sc-:sexp-and-tree o1)) ;; (defun sc-:sexptree-car (o) (let ((sc-context (format "%s.car" sc-context))) (sc-:sexptree (car o)))) (defun sc-:sexptree-cdr (o) (let ((sc-context (format "%s.cdr" sc-context))) (sc-:sexptree (cdr o)))) (defun sc-:sexptree-car+ (o) (sc-:pile (sc-:hl1 sc-context ".") "|" (sc-:sexptree-car o))) (defun sc-:sexptree (o) (if (atom o) (sc-:hl1 sc-context (ee-S o)) (if (null (cdr o)) (sc-:sexptree-car+ o) (sc-:gluewithwires (sc-:sexptree-car+ o) (sc-:sexptree-cdr o))))) (defun sc-:sexp-and-tree (o) (let* ((sexpwithms (sc-:expand (sc-:expand (sc-:sexp o)))) (tree (sc-:sexptree o)) (message "^ Try `C-c C-c' on the nodes")) (sc-:pile sexpwithms "" tree "" message))) ;;; __ _ _ _ ;;; / _(_)_ __ __| | ___| |__ _____ __ ___ ___ _ __ ___ ___ ___ ;;; | |_| | '_ \ / _` | / __| '_ \ / _ \ \ /\ / / / __/ _ \| '_ \/ __|/ _ \/ __| ;;; | _| | | | | (_| | \__ \ | | | (_) \ V V / | (_| (_) | | | \__ \ __/\__ \ ;;; |_| |_|_| |_|\__,_| |___/_| |_|\___/ \_/\_/ \___\___/|_| |_|___/\___||___/ ;;; ;; «find-show-conses» (to ".find-show-conses") ;; Tests: ;; (find-show-conses '((2) 3 ("4" 5) . 6)) ;; (find-2a nil '(find-show-conses '((2) 3 (4 5) . 6))) ;; (find-show-conses-2a '((2) 3 (4 5) . 6)) ;; (find-show-conses-3a '((2) 3 (4 5) . 6)) (defun find-show-conses (o) (find-insertrects (sc-:sexp-and-tree o))) (defun find-show-conses-2a (o) (find-2a nil `(find-show-conses ',o))) (defun find-show-conses-3a (o) (find-3a nil `(find-show-conses ',o) '(find-showregions))) ;;; __ _ _ _ _ ;;; / _(_)_ __ __| | ___| | __ _ ___ ___| |_ _ __ ___ ___ ;;; | |_| | '_ \ / _` |_____ / __| |/ _` / __/ __| __| '__/ _ \/ _ \ ;;; | _| | | | | (_| |_____| (__| | (_| \__ \__ \ |_| | | __/ __/ ;;; |_| |_|_| |_|\__,_| \___|_|\__,_|___/___/\__|_| \___|\___| ;;; ;; «find-classtree» (to ".find-classtree") ;; Tests: ;; (sc-:classchildren 'oclosure) ;; (sc-:toplain (sc-:classtree 'function)) ;; (find-classtree 'function) ;; (find-classtree 't) ;; (find-insertrects-2a (sc-:td "abc" (sc-:rtree "d" "e" "f") "ghi")) ;; (defun sc-:td (tag) `(:rect ((:td1 ,tag)))) (defun sc-:classchildren (symbol) (ee-sort-symbols (cl--class-children (cl-find-class symbol)))) (defun sc-:classtree (symbol) (let* ((children1 (sc-:classchildren symbol)) (children2 (mapcar 'sc-:classtree children1))) (apply 'sc-:rtree (sc-:td symbol) children2))) (defun find-classtree (symbol) (let ((ee-buffer-name (or ee-buffer-name (format "*(find-classtree %s)*" symbol)))) (find-estring (sc-:totext (sc-:classtree symbol))))) (provide 'show-conses) ;; Local Variables: ;; coding: utf-8-unix ;; End: