Skip to content

Instantly share code, notes, and snippets.

@d12frosted
Created September 2, 2026 17:21
Show Gist options
  • Select an option

  • Save d12frosted/d04000068719c1930fb41e3f36982b5a to your computer and use it in GitHub Desktop.

Select an option

Save d12frosted/d04000068719c1930fb41e3f36982b5a to your computer and use it in GitHub Desktop.
textui vs vui-layout.el conformance: same inputs through both layout engines, diffed (yibie/textui#1)
textui 5a8cdd2 vs vui-layout.el, Emacs 31.0.50
category cases mismatches
flex: partition at mins 1 0
flex: partition: oversized first child still gets a row 1 0
flex: grow: remainder to earlier growers 1 0
flex: grow: weighted 1 0
flex: grow: no growers leave surplus unassigned 1 0
flex: shrink by capacity 1 0
flex: shrink: minima that do not fit split the row 1 0
flex: min above natural raises the occupied width 1 0
flex: lone oversized ordinary block clamps 1 0
flex: lone oversized rigid block keeps natural 1 0
flex: rigid block never shrinks in a shared row 1 0
flex: zero total 1 0
flex: zero gap 1 0
flex: zero-width children 1 0
grid: 7 cells in 40, gap 1, columns 4, min 8 1 0
grid: 7 cells in 25, gap 1, columns 4, min 8 1 0
grid: 7 cells in 10, gap 1, columns 4, min 8 1 0
grid: 5 cells in 31, gap 2, columns 3, min 5 1 0
grid: 3 cells in 20, gap 1, columns 2, min 1 1 0
grid: 1 cells in 0, gap 1, columns 3, min 4 1 0
grid: 0 cells in 20, gap 1, columns 3, min 4 1 0
domain: grid without :min-column-width 1 1
domain: grid min-width 0 with gap 0 1 1
compose: overflow widens the column 1 0
compose: wide glyphs 1 0
fuzz: proportional shares 2000 0
fuzz: flex placements 5000 0
fuzz: grid placements 2000 0
fuzz: row composition 1000 0
fuzz: textui elements through make-spec 1000 0
== domain: grid without :min-column-width
input: (5 20 1 3 nil)
textui: (:error "Wrong type argument: number-or-marker-p, nil")
vui: ((:row 0 :column 0 :width 6) (:row 0 :column 1 :width 6) (:row 0 :column 2 :width 6) (:row 1 :column 0 :width 6) (:row 1 :column 1 :width 6))
== domain: grid min-width 0 with gap 0
input: (5 20 0 3 0)
textui: (:error "Arithmetic error")
vui: ((:row 0 :column 0 :width 7) (:row 0 :column 1 :width 7) (:row 0 :column 2 :width 6) (:row 1 :column 0 :width 7) (:row 1 :column 1 :width 7))
;;; layout-conformance.el --- textui vs vui-layout geometry comparison -*- lexical-binding: t; -*-
;; Feeds the same width constraints through textui's layout engine
;; (partition, allocate, shares, grid) and through vui-layout.el, and
;; diffs the resulting geometry. Also compares the two row composers
;; on rendered blocks. This is the conformance set proposed in
;; yibie/textui#1, plus seeded fuzzing over the same input space.
;;
;; Run:
;;
;; emacs -Q --batch -L <vui.el checkout> -L <textui checkout> \
;; -l layout-conformance.el
;;
;; Exit code is the number of categories with mismatches (0 = full
;; conformance); "domain:" categories document inputs one side rejects
;; and are not counted. A summary table goes to stdout; the first mismatches
;; per category are printed with their inputs.
;;
;; Spec mapping (vui -> textui):
;;
;; :natural N, :min M -> :start max(N, M) (M defaults to 0)
;; :minimum M (default: :minimum start)
;; :grow G -> :grow G
;; :rigid t -> :kind :native (:minimum start)
;; otherwise -> :kind :text
;;
;; textui derives :start as max(declared-or-natural, :min-width) and
;; :minimum as :min-width or :start; vui-layout raises :natural to an
;; explicit :min the same way, so both engines occupy at least the
;; minimum. The element-level fuzz below checks that derivation
;; through `textui--make-spec' itself.
;;; Code:
(require 'cl-lib)
(require 'vui-layout)
(require 'textui)
;;;; Deterministic PRNG (same sequence on every Emacs)
(defvar lc--seed 20260902)
(defun lc-random (n)
"Return a pseudo-random integer in [0, N)."
(setq lc--seed (mod (+ (* lc--seed 1103515245) 12345) 2147483648))
(mod (/ lc--seed 65536) n))
(defun lc-pick (list)
"Return a random element of LIST."
(nth (lc-random (length list)) list))
;;;; Spec conversion
(defun lc-textui-child (spec)
"Convert a vui-layout SPEC into a textui internal child plist."
(let* ((min (plist-get spec :min))
(start (max (or (plist-get spec :natural) 0) (or min 0)))
(rigid (plist-get spec :rigid))
(minimum (if rigid start (or min start))))
(list :kind (if rigid :native :text)
:start start
:minimum minimum
:grow (or (plist-get spec :grow) 0))))
(defun lc-vui-spec-from-textui (child)
"Convert a textui internal CHILD plist (from `textui--make-spec') to a vui spec."
(list :natural (plist-get child :start)
:min (plist-get child :minimum)
:grow (plist-get child :grow)
:rigid (eq (plist-get child :kind) :native)))
;;;; Engine drivers
(defun lc-textui-solve (specs total gap)
"Placements from textui's partition + allocate for vui SPECS."
(let ((children (mapcar #'lc-textui-child specs))
(row-index 0)
placements)
(dolist (row (textui--partition-row children total gap))
(let ((column 0))
(dolist (width (textui--allocate-row row total gap))
(push (list :row row-index :column column :width width) placements)
(setq column (1+ column))))
(setq row-index (1+ row-index)))
(nreverse placements)))
(defun lc-textui-grid (count total gap columns min-width)
"Placements from textui's grid math."
(let* ((element (list :columns columns :min-column-width min-width :gap gap))
(column-count (textui--grid-column-count element total))
(available (max 0 (- total (* gap (1- column-count)))))
(tracks (textui--proportional-shares available (make-list column-count 1)))
(row-index 0)
placements)
(dolist (row (textui--partition-fixed (number-sequence 1 count) column-count))
(let ((column 0))
(dolist (_ row)
(push (list :row row-index :column column :width (nth column tracks))
placements)
(setq column (1+ column))))
(setq row-index (1+ row-index)))
(nreverse placements)))
(defun lc-textui-compose (blocks widths gap)
"Row composition as `textui--render-row-line-block' does it after rendering."
(let ((actual (cl-mapcar (lambda (block assigned)
(max assigned (textui--block-width block)))
blocks widths)))
(textui--compose-row-blocks blocks actual gap)))
;;;; Comparison bookkeeping
(defvar lc--results nil "Alist of (CATEGORY CASES MISMATCHES . EXAMPLES).")
(defun lc-safe (thunk)
"Call THUNK; return its value or (:error MESSAGE)."
(condition-case err
(funcall thunk)
(error (list :error (error-message-string err)))))
(defun lc-compare (category input textui-thunk vui-thunk)
"Record one comparison under CATEGORY for INPUT."
(let* ((entry (or (assoc category lc--results)
(car (push (list category 0 0) lc--results))))
(a (lc-safe textui-thunk))
(b (lc-safe vui-thunk)))
(setcar (cdr entry) (1+ (cadr entry)))
(unless (equal a b)
(setcar (cddr entry) (1+ (caddr entry)))
(when (< (length (cdddr entry)) 3)
(setcdr (cddr entry)
(append (cdddr entry)
(list (list :input input :textui a :vui b))))))))
;;;; Named conformance cases (yibie/textui#1)
(defun lc-flex-case (name specs total gap)
(lc-compare (format "flex: %s" name)
(list :specs specs :total total :gap gap)
(lambda () (lc-textui-solve specs total gap))
(lambda () (vui-layout-solve specs total gap))))
(defun lc-named-cases ()
;; Source-order partitioning at minimum widths.
(lc-flex-case "partition at mins"
'((:natural 12 :min 8) (:natural 12 :min 8) (:natural 12 :min 8))
20 1)
(lc-flex-case "partition: oversized first child still gets a row"
'((:natural 30 :min 25) (:natural 5))
20 1)
;; Proportional grow with integer remainder distribution.
(lc-flex-case "grow: remainder to earlier growers"
'((:natural 4 :grow 1) (:natural 4 :grow 1) (:natural 4 :grow 1))
22 1)
(lc-flex-case "grow: weighted"
'((:natural 5 :grow 2) (:natural 5 :grow 1) (:natural 5))
30 1)
(lc-flex-case "grow: no growers leave surplus unassigned"
'((:natural 5) (:natural 5))
30 1)
;; Proportional shrink by natural - minimum capacity.
(lc-flex-case "shrink by capacity"
'((:natural 20 :min 10) (:natural 20 :min 16) (:natural 20 :min 20))
49 2)
;; Minima that do not fit together split the row instead; a deficit
;; beyond capacity is therefore only ever a lone child (clamp case).
(lc-flex-case "shrink: minima that do not fit split the row"
'((:natural 20 :min 18) (:natural 20 :min 18))
30 1)
;; A minimum above the natural width raises the occupied width
;; (textui: start = max(natural, min-width); vui-layout: same).
(lc-flex-case "min above natural raises the occupied width"
'((:natural 3 :min 6) (:natural 2 :grow 1))
11 1)
;; One oversized ordinary block versus one native/rigid block.
(lc-flex-case "lone oversized ordinary block clamps"
'((:natural 20 :min 20))
8 1)
(lc-flex-case "lone oversized rigid block keeps natural"
'((:natural 20 :rigid t))
8 1)
(lc-flex-case "rigid block never shrinks in a shared row"
'((:natural 10 :rigid t) (:natural 20 :min 5))
20 1)
;; Degenerate widths.
(lc-flex-case "zero total" '((:natural 3) (:natural 4)) 0 1)
(lc-flex-case "zero gap" '((:natural 3 :grow 1) (:natural 4 :grow 1)) 20 0)
(lc-flex-case "zero-width children" '((:natural 0 :grow 1) (:natural 0)) 7 1)
;; Grid: column count falls, uneven tracks, incomplete last row.
(dolist (case '((7 40 1 4 8) (7 25 1 4 8) (7 10 1 4 8) (5 31 2 3 5)
(3 20 1 2 1) (1 0 1 3 4) (0 20 1 3 4)))
(pcase-let ((`(,count ,total ,gap ,columns ,min-width) case))
(lc-compare (format "grid: %d cells in %d, gap %d, columns %d, min %d"
count total gap columns min-width)
case
(lambda () (lc-textui-grid count total gap columns min-width))
(lambda () (vui-layout-grid count total gap columns min-width)))))
;; Input-domain differences: textui validates :min-column-width as a
;; positive integer at the element level, so these never reach its
;; engine; vui-layout accepts them. Reported, not counted.
(lc-compare "domain: grid without :min-column-width"
'(5 20 1 3 nil)
(lambda () (lc-textui-grid 5 20 1 3 nil))
(lambda () (vui-layout-grid 5 20 1 3 nil)))
(lc-compare "domain: grid min-width 0 with gap 0"
'(5 20 0 3 0)
(lambda () (lc-textui-grid 5 20 0 3 0))
(lambda () (vui-layout-grid 5 20 0 3 0)))
;; Composition: unbreakable content wider than its assignment.
(lc-compare "compose: overflow widens the column"
nil
(lambda () (lc-textui-compose '(("short" "a much longer line") ("x" "y" "z"))
'(5 3) 1))
(lambda () (vui-layout-compose '(("short" "a much longer line") ("x" "y" "z"))
'(5 3) 1)))
(lc-compare "compose: wide glyphs"
nil
(lambda () (lc-textui-compose '(("漢字" "a") ("b")) '(3 2) 2))
(lambda () (vui-layout-compose '(("漢字" "a") ("b")) '(3 2) 2))))
;;;; Fuzzing
(defun lc-random-spec ()
(let* ((natural (lc-random 31))
(rigid (= (lc-random 6) 0))
(min (pcase (lc-random 3)
(0 nil)
(1 (lc-random (1+ natural)))
(_ (lc-random 31)))) ; may exceed natural: raise rule
(grow (lc-pick '(0 0 0 1 1 2 3 0.5 1.5))))
(append (list :natural natural)
(when min (list :min min))
(when (/= grow 0) (list :grow grow))
(when rigid (list :rigid t)))))
(defun lc-fuzz-flex (n)
(dotimes (_ n)
(let ((specs (cl-loop repeat (1+ (lc-random 8)) collect (lc-random-spec)))
(total (lc-random 80))
(gap (lc-random 4)))
(lc-compare "fuzz: flex placements"
(list :specs specs :total total :gap gap)
(lambda () (lc-textui-solve specs total gap))
(lambda () (vui-layout-solve specs total gap))))))
(defun lc-fuzz-shares (n)
(dotimes (_ n)
(let* ((count (1+ (lc-random 6)))
(amount (lc-random 50))
(weights (cl-loop repeat count collect (lc-pick '(0 1 1 2 3 0.5 2.5))))
(limits (when (= (lc-random 2) 0)
(cl-loop repeat count collect (lc-random 20)))))
(lc-compare "fuzz: proportional shares"
(list :amount amount :weights weights :limits limits)
(lambda () (textui--proportional-shares amount weights limits))
(lambda () (vui-layout-shares amount weights limits))))))
(defun lc-fuzz-grid (n)
(dotimes (_ n)
(let ((count (lc-random 12))
(total (lc-random 80))
(gap (lc-random 4))
(columns (1+ (lc-random 6)))
(min-width (1+ (lc-random 15))))
(lc-compare "fuzz: grid placements"
(list :count count :total total :gap gap
:columns columns :min-width min-width)
(lambda () (lc-textui-grid count total gap columns min-width))
(lambda () (vui-layout-grid count total gap columns min-width))))))
(defun lc-random-line ()
(let ((chars '("a" "b" "c" " " "漢" "字" "-")))
(mapconcat (lambda (_) (lc-pick chars)) (make-list (lc-random 9) nil) "")))
(defun lc-fuzz-compose (n)
(dotimes (_ n)
(let* ((count (1+ (lc-random 4)))
(blocks (cl-loop repeat count
collect (cl-loop repeat (lc-random 4) collect (lc-random-line))))
(widths (cl-loop repeat count collect (lc-random 10)))
(gap (lc-random 3)))
(lc-compare "fuzz: row composition"
(list :blocks blocks :widths widths :gap gap)
(lambda () (lc-textui-compose blocks widths gap))
(lambda () (vui-layout-compose blocks widths gap))))))
(defun lc-random-text-element ()
"A textui :text element with random public layout options."
(let* ((value (mapconcat #'identity
(cl-loop repeat (1+ (lc-random 3)) collect (lc-random-line))
"\n"))
(layout (append (when (= (lc-random 3) 0) (list :width (1+ (lc-random 20))))
(when (= (lc-random 2) 0) (list :min-width (1+ (lc-random 15))))
(when (= (lc-random 2) 0) (list :grow (lc-pick '(1 2 0.5)))))))
(append (list :type :text :value value)
(when layout (list :layout layout)))))
(defun lc-vui-spec-from-element (element)
"Derive a vui spec from a textui :text ELEMENT by the documented rule."
(let* ((layout (plist-get element :layout))
(lines (split-string (plist-get element :value) "\n"))
(measured (apply #'max 0 (mapcar #'string-width lines)))
(declared (plist-get layout :width))
(min-width (plist-get layout :min-width))
(natural (max (or declared measured) (or min-width 0))))
(append (list :natural natural)
(when min-width (list :min min-width))
(when (plist-get layout :grow) (list :grow (plist-get layout :grow))))))
(defun lc-fuzz-elements (n)
"Check the element-to-spec derivation against `textui--make-spec' itself."
(dotimes (_ n)
(let* ((elements (cl-loop repeat (1+ (lc-random 6)) collect (lc-random-text-element)))
(total (lc-random 80))
(gap (lc-random 4))
(textui--next-layout-id nil))
(lc-compare "fuzz: textui elements through make-spec"
(list :elements elements :total total :gap gap)
(lambda ()
(let ((children (mapcar #'textui--make-spec elements))
(row-index 0)
placements)
(dolist (row (textui--partition-row children total gap))
(let ((column 0))
(dolist (width (textui--allocate-row row total gap))
(push (list :row row-index :column column :width width)
placements)
(setq column (1+ column))))
(setq row-index (1+ row-index)))
(nreverse placements)))
(lambda ()
(vui-layout-solve (mapcar #'lc-vui-spec-from-element elements)
total gap))))))
;;;; Report
(defun lc-report ()
(let ((failed 0))
(princ (format "textui %s vs vui-layout.el, Emacs %s\n\n"
(or (getenv "TEXTUI_SHA") "?") emacs-version))
(princ (format "%-58s %6s %10s\n" "category" "cases" "mismatches"))
(dolist (entry (reverse lc--results))
(princ (format "%-58s %6d %10d\n" (car entry) (cadr entry) (caddr entry)))
(when (and (> (caddr entry) 0)
(not (string-prefix-p "domain:" (car entry))))
(setq failed (1+ failed))))
(princ "\n")
(dolist (entry (reverse lc--results))
(when (cdddr entry)
(princ (format "== %s\n" (car entry)))
(dolist (example (cdddr entry))
(princ (format "input: %S\ntextui: %S\nvui: %S\n\n"
(plist-get example :input)
(plist-get example :textui)
(plist-get example :vui))))))
failed))
(defun lc-run ()
(setq lc--results nil)
(lc-named-cases)
(lc-fuzz-shares 2000)
(lc-fuzz-flex 5000)
(lc-fuzz-grid 2000)
(lc-fuzz-compose 1000)
(lc-fuzz-elements 1000)
(lc-report))
(when (and noninteractive (not (getenv "LC_NO_RUN")))
(kill-emacs (lc-run)))
;;; layout-conformance.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment