|
Warning: this is an htmlized version!
The original is here, and the conversion rules are here. |
;; This file:
;; https://anggtwu.net/MAXIMA/edrxbox.lisp.html
;; https://anggtwu.net/MAXIMA/edrxbox.lisp
;; (find-angg "MAXIMA/edrxbox.lisp")
;; Author: Eduardo Ochs <eduardoochs@gmail.com>
;; See: https://anggtwu.net/maxima-edrxbox.html
;; https://anggtwu.net/MAXIMA/edrxbox-examples.lisp.html
;; https://anggtwu.net/MAXIMA/edrxbox-examples.lisp
;; Version: 2025dec12
;; License: GPL v2
;;
;; This file implements a simpler way to write `dim-*' functions.
;; The pretty-printer of Maxima is explained here,
;; (find-maximanode "display2d")
;; with this example:
;;
;; (%i1) x/(x^2+1);
;; x
;; (%o1) ------
;; 2
;; x + 1
;;
;; The example above calls the functions `dim-mquotient'
;; and `dimension-superscript' - two `dim-*' functions.
;; They are defined here:
;;
;; (find-maximagitfile "src/displa.lisp" "(defun dim-mquotient ")
;; (find-maximagitfile "src/displa.lisp" "(defun dimension-superscript ")
;;
;; It is very difficult to write new `dim-*' functions in the style
;; used in "displa.lisp" because the user/programmer has to write the
;; drawing instructions in a cryptic DSL in which many parts of the
;; code look reversed, and has to keep several variables, like the box
;; boundaries, in sync and with sane values, "by hand".
;;
;; This is a work in progress, and the "edrx" in the names of its
;; functions is to remind people that I still intend to rewrite this
;; code and its comments several times before declaring that this code
;; is mature - at this moment it _may be_ just a private hack.
;;
;; For an introduction with screenshots, see:
;;
;; https://anggtwu.net/maxima-edrxbox.html
;;
;; Some edrx-isms:
;; (defun o () (interactive) (find-angg "MAXIMA/edrxbox.lisp"))
;; (defun e () (interactive) (find-angg "MAXIMA/edrxbox-examples.lisp"))
;; (defun oe () (interactive) (find-2a '(o) '(e)))
;; Introduction:
;; «.low-level-terminology» (to "low-level-terminology")
;; «.the-displa-DSL» (to "the-displa-DSL")
;; «.edrxboxes» (to "edrxboxes")
;;
;; Core:
;; «.variables» (to "variables")
;; «.conversions» (to "conversions")
;; «.conversions-tests» (to "conversions-tests")
;; «.bounds» (to "bounds")
;; «.run-in-edrxbox» (to "run-in-edrxbox")
;; «.run-in-edrxbox-tests» (to "run-in-edrxbox-tests")
;; «.debug» (to "debug")
;; «.push» (to "push")
;; «.move» (to "move")
;; «.center» (to "center")
;; Non-trivial `dim-*' functions implemented with edrxbox:
;; «.underbrace» (to "underbrace")
;; «.underbrace-tests» (to "underbrace-tests")
;; «.verbatimbox» (to "verbatimbox")
;; «.verbatimbox-tests» (to "verbatimbox-tests")
;; «.hdw» (to "hdw")
;; «.hdw-tests» (to "hdw-tests")
;;
;;; _ _ _
;;; | | _____ __ | | _____ _____| |
;;; | | / _ \ \ /\ / /____| |/ _ \ \ / / _ \ |
;;; | |__| (_) \ V V /_____| | __/\ V / __/ |
;;; |_____\___/ \_/\_/ |_|\___| \_/ \___|_|
;;;
;; «low-level-terminology» (to ".low-level-terminology")
;;
;; Some low-level terminology
;; ==========================
;; When `display2d' is true Maxima draws objects by running the
;; `dim-*' functions inside the environment created by `displa'.
;; The code is here:
;;
;; (find-maximanode "display2d")
;; (find-maximagitfile "src/displa.lisp" "(defun displa ")
;; (find-maximagitfile "src/displa.lisp" "(cond ($display2d")
;; (find-maximagitfile "src/displa.lisp" "(dimension form ")
;; (find-maximagitfile "src/displa.lisp" "(output ")
;; (find-maximagitfile "src/displa.lisp" "(defun dimension ")
;; (find-maximagitfile "src/displa.lisp" "(safe-get (caar form) 'dimension)")
;;
;; Here are some `dim-*' functions:
;;
;; (find-maximagitfile "src/displa.lisp" "(defun dim-mquotient ")
;; (find-maximagitfile "src/displa.lisp" "(defun dim-$matrix ")
;; (find-maximagitfile "src/displa.lisp" "(defun dim-mbox ")
;; (find-maximagitfile "src/displa.lisp" "(defun dimension-superscript ")
;;
;; This code shows how to get from "^" to `dimension-superscript':
#|
* (eepitch-maxima)
* (eepitch-kill)
* (eepitch-maxima)
to_lisp();
#$a^b$
(caar #$a^b$)
(safe-get (caar #$a^b$) 'dimension)
(define-symbol "^")
(describe (define-symbol "^"))
(get (define-symbol "^") 'mheader)
(car (get (define-symbol "^") 'mheader))
(describe (car (get (define-symbol "^") 'mheader)))
(get (car (get (define-symbol "^") 'mheader)) 'dimension)
|#
;; I will say, by a trivial abuse of language, that:
;;
;; the `mheader' property of "^" is `mexpt',
;; the `dimension' property of `mexpt' is `dimension-superscript',
;; the `dimension' property of "^" is `dimension-superscript',
;;
;; Usually dimension properties are assigned by running the macro
;; `displa-def'. For example:
;;
;; (find-maximagitfile "src/displa.lisp" "(displa-def mquotient ")
;; (find-maximagitfile "src/displa.lisp" "(displa-def mexpt" "dimension-superscript)")
;; (find-maximagitfile "src/displa.lisp" "(displa-def $matrix dim-$matrix)")
;;
;; but when people discuss setting dimension properties in the mailing
;; list they usually usually use a `setf' directly instead of calling
;; `displa-def' - they use this idiom:
;;
;; (setf (get '$barematrix 'dimension) 'dim-$barematrix)
;;
;; I use that a lot. See, for example:
;;
;; (find-angg "MAXIMA/2025-displa-tex.lisp" "barematrix")
;;; _____ _ _ _ _ ____ ____ _
;;; |_ _| |__ ___ __| (_)___ _ __ | | __ _ | _ \/ ___|| |
;;; | | | '_ \ / _ \ / _` | / __| '_ \| |/ _` | | | | \___ \| |
;;; | | | | | | __/ | (_| | \__ \ |_) | | (_| | | |_| |___) | |___
;;; |_| |_| |_|\___| \__,_|_|___/ .__/|_|\__,_| |____/|____/|_____|
;;; |_|
;;
;; «the-displa-DSL» (to ".the-displa-DSL")
;;
;; The displa language
;; ===================
;; One of the arguments of the `dim-*' functions is always a variable
;; called "result", that contains drawing instructions; that variable
;; is modified by the `dim-*' function - with a series of "push"es -
;; and its new value is returned. The drawing instructions are in a
;; language, or DSL, that is described VERY briefly in these comments
;; in "displa.lisp":
;;
;; <dimension string> ::= () | (<string element> . <dimension string>)
;; <string element> ::= character |
;; (<column-relative> <row-relative> . <dimension string>) |
;; (<drawing function> . args)
;; <column-relative> ::= <fixnum>
;; <row-relative> ::= <fixnum>
;; <drawing function> ::= D-HBAR | D-VBAR | D-INTEGRALSIGN | ...
;;
;; When a character appears in a dimension string, it is printed and
;; the cursor moves forward a single position. (The variable OLDCOL is
;; incremented) When a form with a fixnum car is encountered, the
;; first two elements of the form are taken to be relative displacements
;; for OLDCOL and OLDROW. *** NOTE *** After drawing the cddr of the form,
;; OLDROW is reset to its original value, but OLDCOL is left in the new
;; position. Why this is done is beyond me. It only appears to complicate
;; things.
;;
;; Here is a link to where the comments apprear in the source - they
;; appear just above the "defun output":
;;
;; (find-maximagitfile "src/displa.lisp" "(defun output ")
;;
;; I will refer to that language as the "displa language", or the
;; "displa DSL". Here is an example of code in that in that language,
;;
;; ((3 0)
;; (-2 -2 #\g)
;; (-4 -1 #\f #\e)
;; (0 0 #\d #\c #\b #\a))
;;
;; and a translation of it for humans - note that many things in it
;; look reversed:
;;
;; Start at (x,y)=(0,0). Typeset "abcd". Now (x,y)=(4,0).
;; Move by x+=-4, y=-1. Now (x,y)=(0,-1). Typeset "ef". Now (x,y)=(2,-1).
;; Move by x+=-2, y=-2. Now (x,y)=(0,-2). Typeset "g". Now (x,y)=(1,-2).
;; Move by x+=3, y=0. Now (x,y)=(4,0), and we're after the "d" of the "abcd".
;;
;; Besides modifying the "result" the `dim-*' functions also return
;; the height, depth, and width of what they drew - and they do that
;; by changing the global variables `height', `depth', and `width'.
;; Also, in some cases - here I don't understand the rules well - the
;; `dim-*' functions are expected to set (x,y)=(width,0) before
;; returning, and some things may break when that convention is not
;; followed.
;;
;; The displa DSL only started to make sense to me when I studied, and
;; understood, Robert Dodier's `dim-antideriv', that is written in a
;; style that is more modern than the one used in "displa.lisp". See:
;;
;; (find-maximamsg "59178281 202504 29" "RDodier: dim-antideriv.lisp")
;; (find-maximamsg "59266596 202511 30" "RDodier/Edrx: One less bug...")
;; https://anggtwu.net/MAXIMA/dim-antideriv.lisp.html
;; https://anggtwu.net/MAXIMA/dim-antideriv.lisp
;; (find-angg "MAXIMA/dim-antideriv.lisp")
;;; _____ _ _
;;; | ____|__| |_ ____ _| |__ _____ _____ ___
;;; | _| / _` | '__\ \/ / '_ \ / _ \ \/ / _ \/ __|
;;; | |__| (_| | | > <| |_) | (_) > < __/\__ \
;;; |_____\__,_|_| /_/\_\_.__/ \___/_/\_\___||___/
;;;
;; «edrxboxes» (to ".edrxboxes")
;;
;; The edrxbox "language"
;; ======================
;; Here is an alternative - or, rather, a front-end - to the displa
;; language. If we run this
#|
* (eepitch-maxima)
* (eepitch-kill)
* (eepitch-maxima)
to_lisp();
(load "edrxbox.lisp")
(run-in-edrxbox
(edrxboxvars-push-x-y-string 0 0 "abcd")
(edrxboxvars-push-x-y-string 0 -1 "de")
(edrxboxvars-push-x-y-string 0 -2 "f")
(edrxboxvars-push-x-y-end)
)
(to-maxima)
|#
;; the output of the `run-in-edrxbox' sexp is this plist:
;;
;; (:X 4 :Y 0 :HEIGHT 1 :DEPTH 2 :WIDTH 4
;; :RESULT ((3 0)
;; (-2 -2 #\f)
;; (-4 -1 #\e #\d)
;; (0 0 #\d #\c #\b #\a)))
;;
;; Here's how the `(run-in-edrxbox CODE)' above works.
;; `run-in-edrxbox' is a macro that:
;;
;; 1. uses a `let*' to create an environment in which the
;; global/special variables `box-x', `box-y', `box-height',
;; `box-depth', `box-width' and `box-result' describe an empty
;; box,
;;
;; 2. runs the sexps in CODE,
;;
;; 3. returns the values of the `box-*' variables in a plist, and
;; closes the `let*'.
;;
;; 4. The `edrxbox-*' functions keep track of the current (x,y)
;; position, of the size of the drawing, and of the current
;; "result" in the `box-*' variables, and adjust them at every
;; step.
;;
;; If we run the example below - in which we replaced `run-in-edrxbox'
;; of the example above by a `run-in-edrxbox-dim' inside a `dim-*'
;; function,
#|
* (eepitch-maxima)
* (eepitch-kill)
* (eepitch-maxima)
to_lisp();
(load "edrxbox.lisp")
(setf (get '$foo 'dimension) 'dim-$foo)
(defun dim-$foo (form result)
(declare (ignorable form result))
(run-in-edrxbox-dim
(edrxboxvars-push-x-y-string 0 0 "abcd")
(edrxboxvars-push-x-y-string 0 -1 "de")
(edrxboxvars-push-x-y-string 0 -2 "f")
(edrxboxvars-push-x-y-end)))
(to-maxima)
linel : 40;
[box(foo())];
|#
;; The output of the last line is:
;;
;; (%i3) [box(foo())];
;; ╔════╗
;; (%o3) [║abcd║]
;; ║de ║
;; ║f ║
;; ╚════╝
;;; __ __ _ _ _
;;; \ \ / /_ _ _ __(_) __ _| |__ | | ___ ___
;;; \ \ / / _` | '__| |/ _` | '_ \| |/ _ \/ __|
;;; \ V / (_| | | | | (_| | |_) | | __/\__ \
;;; \_/ \__,_|_| |_|\__,_|_.__/|_|\___||___/
;;;
;; «variables» (to ".variables")
;; The code in "displm.lisp" and "displa.lisp" keep the state of the
;; current box in many special/global variables - `height', `depth',
;; `width', and many others - and put the drawing instructions in
;; `result', that is always an argument and a return value instead of
;; a being a global variable.
;;
;; I will refer to the variables `height', `depth', `width', and
;; `result' - as the "dimvars".
;;
;; Here we declare the special variables `box-height', `box-depth',
;; `box-width', `box-result', `box-x' and `box-y'; I will call them
;; the "edrxboxvars". By keeping the current (x,y) position in the
;; variables `box-x' and `box-y' we can make our code much simpler -
;; in the sense of more high-level, easier to read, write, and debug.
;;
;; See:
;; (find-maximagitfile "src/displm.lisp" " (special")
;; (find-maximagitfile "src/displa.lisp" "(defun displa ")
;; (find-maximagitfile "src/displa.lisp" "(defun displa " "width")
;; (find-maximagitfile "src/displa.lisp" "(defun dim-mquotient ")
;; (find-maximagitfile "src/displa.lisp" "(defun dim-$matrix ")
;; (find-maximagitfile "src/displa.lisp" "(defun dim-mbox ")
(defvar box-x)
(defvar box-y)
(defvar box-width)
(defvar box-height)
(defvar box-depth)
(defvar box-result)
;;; ____ _
;;; / ___|___ _ ____ _____ _ __ ___(_) ___ _ __ ___
;;; | | / _ \| '_ \ \ / / _ \ '__/ __| |/ _ \| '_ \/ __|
;;; | |__| (_) | | | \ V / __/ | \__ \ | (_) | | | \__ \
;;; \____\___/|_| |_|\_/ \___|_| |___/_|\___/|_| |_|___/
;;;
;; «conversions» (to ".conversions")
;; Our basic conversions are these ones:
;;
;; dimvars <--> edrxboxvars <--> edrxbox <--- string
;; <--- from
;; Some of them are very tricky.
(defun edrxboxvars-to-edrxbox ()
(list :x box-x
:y box-y
:height box-height
:depth box-depth
:width box-width
:result box-result))
(defun edrxboxvars-from-edrxbox (edrxbox)
(setq box-x (getf edrxbox :x)
box-y (getf edrxbox :y)
box-height (getf edrxbox :height)
box-depth (getf edrxbox :depth)
box-width (getf edrxbox :width)
box-result (getf edrxbox :result)))
(defmacro edrxboxvars-to-dimvars ()
"Export the current `box-*' variables to `height', `depth', `width', and `result'.
`result' is always a local variable, so this needs to be a macro.
This macro is called at the end of `run-in-edrxbox-dim'."
`(progn
(setq height box-height
depth box-depth
width box-width
result `(,@box-result ,@result)) ; <- non-idempotent!
(update-heights height depth)
result))
(defun edrxbox-from-string (string)
(let* ((restring (reverse (exploden string)))
(width (length restring)))
(list :width width :height 1 :depth 0 :result restring)))
;; See:
;; (find-maximagitfile "src/displa.lisp" "(defun displa " "(let ")
;; (find-maximagitfile "src/displa.lisp" "(defun displa " "(dimension ")
(defun edrxbox-from-form (form)
(let* ((level 0)
(size 2)
(result (dimension form nil 'mparen 'mparen nil 0))) ; changes the dimvars!
(list :width width :height height :depth depth :result result)))
;; «conversions-tests» (to ".conversions-tests")
#|
* (eepitch-maxima)
* (eepitch-kill)
* (eepitch-maxima)
o1 : 'integrate(f(x), x, a, b);
to_lisp();
(load "edrxbox.lisp")
(edrxbox-from-string "abcd")
(edrxbox-from-form #$o1$)
(to-maxima)
|#
;;; _ _ _
;;; _ __ _ _ _ __ (_)_ __ ___ __| |_ ____ _| |__ _____ __
;;; | '__| | | | '_ \ _____| | '_ \ _____ / _ \/ _` | '__\ \/ / '_ \ / _ \ \/ /
;;; | | | |_| | | | |_____| | | | |_____| __/ (_| | | > <| |_) | (_) > <
;;; |_| \__,_|_| |_| |_|_| |_| \___|\__,_|_| /_/\_\_.__/ \___/_/\_\
;;;
;; «run-in-edrxbox» (to ".run-in-edrxbox")
;; These functions and macros implement the environments
;; `run-in-edrxbox' and `run-in-edrxbox-dim'. I still don't
;; know in which cases `edrxbox-push-x-y-end' is needed, so
;; I'm keeping it out the `run-*' functions - we have to
;; call it explicitly.
(defmacro run-in-edrxbox (&rest code)
"Run CODE in an edrxbox environment, and return an edrxbox."
`(let* ((box-x 0) (box-y 0)
(box-height 1) (box-depth 0) (box-width 0)
(box-result nil))
,@code
(edrxboxvars-to-edrxbox)))
(defmacro run-in-edrxbox-dim (&rest code)
"This is a variant of `run-in-edrxbox' that runs CODE inside a `dim-*' function."
`(let* ((box-x 0) (box-y 0)
(box-height 1) (box-depth 0) (box-width 0)
(box-result nil))
,@code
(edrxboxvars-to-dimvars)))
;; «run-in-edrxbox-tests» (to ".run-in-edrxbox-tests")
#|
* (eepitch-maxima)
* (eepitch-kill)
* (eepitch-maxima)
load("edrxbox.lisp");
to_lisp();
(defun foo-core ()
(edrxboxvars-push-x-y-string 0 0 "abcd")
(edrxboxvars-push-x-y-string 0 -1 "de")
(edrxboxvars-push-x-y-string 0 -2 "f")
(edrxboxvars-push-x-y-end))
(run-in-edrxbox (foo-core))
(setf (get '$foo 'dimension) 'dim-$foo)
(defun dim-$foo (form result)
(declare (ignorable form result))
(run-in-edrxbox-dim (foo-core)))
(to-maxima)
[box(foo())];
|#
;; «debug» (to ".debug")
;; For print debugging.
(defun pr (&rest list) (format t "~S~%" list))
(defun prall ()
(pr :x box-x :y box-y
:width box-width :height box-height :depth box-depth
:result box-result))
;; «bounds» (to ".bounds")
(defun edrxboxvars-adjust-bounds (x y edrxbox)
"This function adjusts the box size in the `box-*' variables
to make sure that the given edrxbox - draw at (x,y) - fits inside it."
(setq box-width (max box-width (+ (getf edrxbox :width) x))
box-height (max box-height (+ (getf edrxbox :height) y))
box-depth (max box-depth (- (getf edrxbox :depth) y))))
;; «push» (to ".push")
(defun edrxboxvars-push (item) (push item box-result))
(defun edrxboxvars-push-x-y-edrxbox (x y edrxbox)
(edrxboxvars-push `(,(- x box-x) ,y ,@(getf edrxbox :result)))
(edrxboxvars-adjust-bounds x y edrxbox)
(setq box-x (+ x (getf edrxbox :width))
box-y y))
(defun edrxboxvars-push-x-y-string (x y string)
(edrxboxvars-push-x-y-edrxbox x y (edrxbox-from-string string)))
(defun edrxboxvars-push-x-y-end ()
"Push to `box-results' a command to move to (x,y)=(box-width,0)."
(edrxboxvars-push-x-y-string box-width 0 ""))
;; «move» (to ".move")
(defun edrxbox-move (dx dy edrxbox)
(setq dx (truncate dx))
(setq dy (truncate dy))
(run-in-edrxbox
(edrxboxvars-push-x-y-edrxbox dx dy edrxbox)
(edrxboxvars-push-x-y-end)))
;; «center» (to ".center")
(defun edrxbox-hcenter (bigwidth edrxbox)
(let* ((edrxboxwidth (getf edrxbox :width))
(extrawidth (- bigwidth edrxboxwidth)))
(edrxbox-move (/ extrawidth 2) 0 edrxbox)))
(defun edrxbox-vcenter (edrxbox)
(let* ((h (getf edrxbox :height))
(d (getf edrxbox :depth))
(extradepth (- d (- h 1))))
(edrxbox-move 0 (/ extradepth 2) edrxbox)))
;; Local Variables:
;; coding: utf-8-unix
;; End: