A Xah Lee Challenge

Over at ergoemacs.org, Xah Lee posses the following challenge: Find a simple and general method to create the following text.

(global-set-key (kbd "<menu> g a") "A")
(global-set-key (kbd "<menu> g b") "B")
(global-set-key (kbd "<menu> g c") "C")
(global-set-key (kbd "<menu> g d") "D")
(global-set-key (kbd "<menu> g e") "E")
(global-set-key (kbd "<menu> g f") "F")
(global-set-key (kbd "<menu> g g") "G")
(global-set-key (kbd "<menu> g h") "H")
(global-set-key (kbd "<menu> g i") "I")
(global-set-key (kbd "<menu> g j") "J")
(global-set-key (kbd "<menu> g k") "K")
(global-set-key (kbd "<menu> g l") "L")
(global-set-key (kbd "<menu> g m") "M")
(global-set-key (kbd "<menu> g n") "N")
(global-set-key (kbd "<menu> g o") "O")
(global-set-key (kbd "<menu> g p") "P")
(global-set-key (kbd "<menu> g q") "Q")
(global-set-key (kbd "<menu> g r") "R")
(global-set-key (kbd "<menu> g s") "S")
(global-set-key (kbd "<menu> g t") "T")
(global-set-key (kbd "<menu> g u") "U")
(global-set-key (kbd "<menu> g v") "V")
(global-set-key (kbd "<menu> g w") "W")
(global-set-key (kbd "<menu> g x") "X")
(global-set-key (kbd "<menu> g y") "Y")
(global-set-key (kbd "<menu> g z") "Z")

As I write this, someone has already contributed a solution using a keyboard macro so I’ll give one in Elisp. The core of the solution is just the three lines

(dotimes (c 26)
  (insert (format "(global-set-key (kbd \"<menu> g %c\") \"%c\")\n"
                  (+ ?a c) (+ ?A c))))

If you’re going to use this more than once, then you should probably turn it into a function like so:

(defun a-to-z ()
  (interactive)
  (dotimes (c 26)
    (insert (format "(global-set-key (kbd \"<menu> g %c\") \"%c\")\n"
                    (+ ?a c) (+ ?A c)))))

Then you can just type 【Meta+xa-to-z whenever you need the text.

If it’s a one-off you can just type the 3 lines into your buffer, execute them with 【Ctrl+x Ctrl+e】 and then delete the Elisp.

If you don’t like having to erase the Elisp or you are going to need the text more than once in the buffer you can type

(with-current-buffer "name-of-working-buffer"
  (dotimes (c 26)
    (insert (format "(global-set-key (kbd \"<menu> g %c\") \"%c\")\n"
                    (+ ?a c) (+ ?A c)))))

in the scratch buffer and execute it with 【Ctrl+x Ctrl+e】 there. It will then put the text in your working buffer.

This entry was posted in Programming and tagged , . Bookmark the permalink.

4 Responses to A Xah Lee Challenge

  1. Phil says:

    Given 26 duplicate lines, rectangular numbering with C-u C-x r N (and arguments ?a and %c) is a simple way to produce a column of a-z.

  2. Noam says:

    Instead of generating repetitive elisp code, why not write elisp that is less repetitive:

    (dotimes (c 26)
    (global-set-key (vector 'menu ?g (+ ?a c)) (string (+ ?A c))))

  3. bard bloom says:

    I use my insert-patterned command, which I wrote a long time ago and probably should toss on a website now that the web exists.

    Anyways, the command and its minibuffer arguments are this:

    m-x insert-patterned
    Pattern: (global-set-key (kbd ” g %[i c]“) “%[(- i 32) c]“)
    From: ?a
    To: ?z

    You can have two, three, or even four variables to loop over, not that I’ve used the four-variable version more than once in 22 years.

    – Bard

    ;; Repetitititive insertion functions.
    ;; Copyright (C) 1990 Bard Bloom

    ;; WARNING: This file uses lots of macros.
    ;; Interactively, the insert-patterned-3 and -4 functions are quite slow.
    ;; Byte-compiled, they go like blazes,
    ;; ** BUT ** there are so many macros that the ordinary `max-lisp-eval-depth’ of 200
    ;; is not enough. Increase it (say, to 400) before compiling this file.

    ;; A gizmo for quickly generating text with a pattern, and in particular
    ;; arithmetic progressions. For example, you might want to
    ;; create text looking like:
    ;; `a’ –> 97 and the square of that is 9409
    ;; `b’ –> 98 and the square of that is 9604
    ;; `c’ –> 99 and the square of that is 9801
    ;; `d’ –> 100 and the square of that is 10000
    ;; `e’ –> 101 and the square of that is 10201

    ;; This was generated by the command
    ;; m-x insert-patterned
    ;; with arguments “;; `%c’ –> %d and the square of that is %(* i i)”,
    ;; ?a,
    ;; and ?e
    ;;

    ;; The numeric arguments are just evaluated lisp expressions, so you
    ;; can generate
    ;; 1. 4367381
    ;; 2. -3193086
    ;; 3. -1673567
    ;; 4. -1430400
    ;; 5. -1060121
    ;; by m-x insert-patterned “;; %d. %(random)”,
    ;; 1,
    ;; (+ 1 (* 2 2))
    ;; should this suit your fancy.

    ;; insert-patterned uses the variable i for its index (as in the first example).
    ;; There are also insert-patterned-2 through insert-patterned-4 functions,
    ;; which use j, k, and l as well. So, you really can generate:
    ;; decimal[0000]=0
    ;; decimal[0001]=1
    ;; decimal[0010]=2
    ;; decimal[0011]=3
    ;; decimal[0100]=4
    ;; decimal[0101]=5
    ;; decimal[0110]=6
    ;; decimal[0111]=7
    ;; decimal[1000]=8
    ;; decimal[1001]=9
    ;; decimal[1010]=10
    ;; decimal[1011]=11
    ;; decimal[1100]=12
    ;; decimal[1101]=13
    ;; decimal[1110]=14
    ;; decimal[1111]=15
    ;;
    ;; with one command.

    ;; You can also iterate over lists:
    ;; My desk is cluttered
    ;; My desk is a real mess
    ;; My desk is very hard to see
    ;; My desk is buried
    ;;
    ;; from m-x insert-patterned
    ;; “;; My desk is %s”
    ;; (cluttered “a real mess” “very hard to see” buried)

    ;; This file is is intended for use with GNU Emacs, and may be added to it
    ;; if the Free Software Foundation so wishes.

    ;; GNU Emacs is distributed in the hope that it will be useful,
    ;; but WITHOUT ANY WARRANTY. No author or distributor
    ;; accepts responsibility to anyone for the consequences of using it
    ;; or for whether it serves any particular purpose or works at all,
    ;; unless he says so in writing. Refer to the GNU Emacs General Public
    ;; License for full details.

    ;; Everyone is granted permission to copy, modify and redistribute
    ;; GNU Emacs, but only under the conditions described in the
    ;; GNU Emacs General Public License. A copy of this license is
    ;; supposed to have been given to you along with GNU Emacs so you
    ;; can know your rights and responsibilities. It should be in a
    ;; file named COPYING. Among other things, the copyright notice
    ;; and this notice must be preserved on all copies.

    (require ‘cl)

    (defmacro insert-patterned-looper (var low high
    &rest body)
    ;; For VAR from LOW to HIGH do BODY if LOW is a number numbers,
    ;;For VAR over LOW do BODY (ignoring HIGH) if HIGH is a list.
    ;;It also does a (put VAR ‘insert-pat PUT-NUMERIC) or …PUT-LIST
    ;;depending on which it is. Insert-patterned uses this feature.
    ;;* LOW and HIGH are evaluated once each.
    ;;* VAR is let-bound.
    ;;* The decision is run-time (and uses numberp).
    ;;* HIGH is eval’ed even if LOW is a list.
    (let ((low-save (gensym))
    (high-save (gensym)))
    `
    (let ((, var)
    ( ,low-save ,low)
    ( ,high-save ,high)
    )
    (cond
    ((numberp ,low-save)
    (cond ((numberp ,high-save)
    (setq ,var ,low-save)
    (while (<= ,var ,high-save)
    ,@ body
    (incf ,var)
    ))
    (t (error "The high bound for variable %s is not a number."
    (quote ,var)))))
    ((listp , low-save)
    (dolist (,var ,low-save)
    ,@ body))
    (t (error "I don't know how to do the %s loop" (quote (, var))))
    ))))

    (defvar insert-patterned-last-input ""
    "Last pattern string for insert-patterned. Not much point in playing
    with it.")
    (defvar insert-patterned-last-input-2 ""
    "Last pattern string for insert-patterned. Not much point in playing
    with it.")
    (defvar insert-patterned-last-input-3 ""
    "Last pattern string for insert-patterned. Not much point in playing
    with it.")
    (defvar insert-patterned-last-input-4 ""
    "Last pattern string for insert-patterned. Not much point in playing
    with it.")

    (defun insert-patterned (pattern low high &optional no-line-breaks)
    "Insert lines with a similar structure but some variation into the file.
    It repeatedly inserts PATTERN in the file, varying the global variable `i'
    from LOW to HIGH (if LOW is a number), or just along LOW (if LOW is a list).

    Interactively, it reads PATTERN, then LOW, then HIGH if LOW was a number.
    * It tries to evaluate LOW as a lisp expression. If it works, it uses that
    value; otherwise, it uses the text of LOW.
    * If LOW is a number, it then reads and tries to evaluate HIGH.
    The variable `low-i' is bound to the value it read for LOW, so you can
    write things like `(+ low-i 10)'.
    * It is smart if you use the %i construct in patterns; it will use %s
    or %d format as appropriate.

    PATTERN is used somewhat as a format. If the following
    constructs occur within the PATTERN, they are replaced by appropriate
    string:
    %d — index as number
    %i — index as number or string
    %c — index as char
    %(X) — eval the expression (X), insert its value (made into a string)
    %% — a %-sign
    %[X] — the value of the expression X, made into a string.
    %[X c] — the value of the expression X, printed with format c.

    For example, with
    PATTERN = \" a[%i] := %(* 2 i); \"
    LOW = 0
    HIGH = 3
    this command would insert
    a[0] := 0;
    a[1] := 2;
    a[2] := 4;
    a[3] := 6;

    Interactively, LOW and HIGH are evaluated expressions:
    PATTERN = \" ascii_%c := %i; \"
    LOW = ?a
    HIGH = ?d
    would insert
    ascii_a := 97;
    ascii_b := 98;
    ascii_c := 99;
    ascii_d := 100;

    A simple hex ASCII table could be generated with
    PATTERN = \" %c – %[i 2x]\".
    The pattern %[i 2x] prints the value of the expression i according to the
    format string `%2x', which is two-place hexadecimal.
    See `format' for more details.

    There is a good deal of overlap between the %(…) and %[... ...] patterns.
    %(X) and %[(X)] are identical. The %[...] can be used for inserting the values
    of variables — %[x] inserts the value of x, while %(x) tries to call the
    function x — and the second argument in the %[...] construct can specify
    a format.

    If the prefix argument NO-LINE-BREAKS is true, then the insertions
    are not separated by anything; otherwise, they are separated by line-breaks.

    Finally, it remembers patterns between invocations."
    (interactive
    (let (low-i)
    (list
    (insert-patterned-read-string-memory "Pattern:" 'insert-patterned-last-input)
    (setq low-i (insert-patterned-try-to-eval (read-from-minibuffer "From/List: " nil nil t)))
    (if (numberp low-i) (insert-patterned-try-to-eval (read-from-minibuffer "To: " nil nil t)) '())
    current-prefix-arg)))
    (let* ((format+args (insert-pattern-internal pattern 1))
    (fmt (first format+args))
    (args (second format+args)))
    (insert-patterned-looper i low high
    (insert
    (apply (function format) fmt (mapcar (function eval) args))
    (if no-line-breaks "" "\n"))
    ))
    )

    (defun insert-patterned-2 (pattern lowi highi lowj highj &optional no-line-breaks)
    "Inserts copies of PATTERN, varying `i' and `j' between limits.
    Very much like insert-patterned, except that it has two variables `i' and `j'.
    `i' varies more slowly than `j'. Patterns are the same as insert-pattern
    (q.v.) except that we also allow:
    %j — second index as number.
    If you want to insert `j' as a character, use `%[j c]'."
    (interactive
    (let (low-i low-j)
    (list
    (insert-patterned-read-string-memory "Pattern:" 'insert-patterned-last-input-2)
    (setq low-i (insert-patterned-try-to-eval (read-from-minibuffer "i from/list: " nil nil t)))
    (if (numberp low-i) (insert-patterned-try-to-eval (read-from-minibuffer "i to: " nil nil t)) '())
    (setq low-j (insert-patterned-try-to-eval (read-from-minibuffer "j from/list: " nil nil t)))
    (if (numberp low-j) (insert-patterned-try-to-eval (read-from-minibuffer "j to: " nil nil t)) '())
    current-prefix-arg)))
    (let* ((format+args (insert-pattern-internal pattern 2))
    (fmt (first format+args))
    (args (second format+args)))
    (insert-patterned-looper i lowi highi
    (insert-patterned-looper j lowj highj
    (insert
    (apply (function format) fmt (mapcar (function eval) args))
    (if no-line-breaks "" "\n"))
    ))))

    (defun insert-patterned-3 (pattern lowi highi lowj highj lowk highk &optional no-line-breaks)
    "Very much like insert-patterned, except that it has three variables:
    `i',`j',`k'.
    `i' varies more slowly than `j', which is in turn slower than `k'.
    Patterns are the same as insert-pattern (q.v.)
    except that we also allow:
    %j — second index as number.
    %k — third index as number
    If you want to insert `j' as a character, use %[j c]. And so on."
    (interactive
    (let (low-i low-j low-k)
    (list
    (insert-patterned-read-string-memory "Pattern:" 'insert-patterned-last-input-3)
    (setq low-i (insert-patterned-try-to-eval (read-from-minibuffer "i from/list: " nil nil t)))
    (if (numberp low-i) (insert-patterned-try-to-eval (read-from-minibuffer "i to: " nil nil t)) '())
    (setq low-j (insert-patterned-try-to-eval (read-from-minibuffer "j from/list: " nil nil t)))
    (if (numberp low-j) (insert-patterned-try-to-eval (read-from-minibuffer "j to: " nil nil t)) '())
    (setq low-k (insert-patterned-try-to-eval (read-from-minibuffer "k from/list: " nil nil t)))
    (if (numberp low-k) (insert-patterned-try-to-eval (read-from-minibuffer "k to: " nil nil t)) '())
    current-prefix-arg)))
    (let* ((format+args (insert-pattern-internal pattern 3))
    (fmt (first format+args))
    (args (second format+args)))
    (insert-patterned-looper i lowi highi
    (insert-patterned-looper j lowj highj
    (insert-patterned-looper k lowk highk
    (insert
    (apply (function format) fmt (mapcar (function eval) args))
    (if no-line-breaks "" "\n"))
    )))))

    (defun insert-patterned-4 (pattern lowi highi lowj highj lowk highk
    lowl highl &optional no-line-breaks)
    "Very much like insert-patterned, except that it has four variables:
    `i',`j',`k',`l'.
    `i' varies more slowly than `j', which is in turn slower than `k', and
    `l' is fastest.
    Patterns are the same as insert-pattern (q.v.)
    except that we also allow:
    %j — second index as number.
    %k — third index as number
    %l — fourth index as number.
    If you want to insert `j' as a character, use %[j c], and
    similarly for `k' and `l'."
    (interactive
    (let (low-i low-j low-k)
    (list
    (insert-patterned-read-string-memory "Pattern:" 'insert-patterned-last-input-4)
    (setq low-i (insert-patterned-try-to-eval (read-from-minibuffer "i from/list: " nil nil t)))
    (if (numberp low-i) (insert-patterned-try-to-eval (read-from-minibuffer "i to: " nil nil t)) '())
    (setq low-j (insert-patterned-try-to-eval (read-from-minibuffer "j from/list: " nil nil t)))
    (if (numberp low-j) (insert-patterned-try-to-eval (read-from-minibuffer "j to: " nil nil t)) '())
    (setq low-k (insert-patterned-try-to-eval (read-from-minibuffer "k from/list: " nil nil t)))
    (if (numberp low-k) (insert-patterned-try-to-eval (read-from-minibuffer "k to: " nil nil t)) '())
    (setq low-l (insert-patterned-try-to-eval (read-from-minibuffer "l from/list: " nil nil t)))
    (if (numberp low-l) (insert-patterned-try-to-eval (read-from-minibuffer "l to: " nil nil t)) '())
    current-prefix-arg)))
    (let* ((format+args (insert-pattern-internal pattern 4))
    (fmt (first format+args))
    (args (second format+args)))
    (insert-patterned-looper i lowi highi
    (insert-patterned-looper j lowj highj
    (insert-patterned-looper k lowk highk
    (insert-patterned-looper l lowl highl
    (insert
    (apply (function format) fmt (mapcar (function eval) args))
    (if no-line-breaks "" "\n"))
    ))))))

    (defun insert-patterned-string-cdr (s)
    (cond
    (( \”97\”. X can be anything, but the conversion
    isn’t necessarily all that smart.”
    (cond
    ((stringp x) x)
    ((symbolp x) (symbol-name x))
    ((numberp x) (int-to-string x))
    (t (prin1-to-string x))))

    (defun insert-pattern-internal (pattern nloops)
    (let ((args ‘())
    (pattern-tail pattern)
    (p “”)
    m rfs
    insert-command
    )
    (while (setq m (string-match “^\\([^%]*\\)%\\(.\\)” pattern-tail))
    (setq p (concat p (substring pattern-tail (match-beginning 1) (match-end 1)) “%”))
    (setq pattern-tail (substring pattern-tail (match-beginning 2)))
    (case (string-to-char pattern-tail)
    (?% (setq p (concat p “%”)))
    ((?c ?d)
    (push ‘i args)
    (setq p (concat p (substring pattern-tail 1 1)))
    )
    ((?\[)
    (setq rfs (read-from-string pattern-tail))
    (setq pattern-tail (substring pattern-tail (cdr rfs)))
    (cond
    ((= (length (car rfs)) 1)
    (setq p (concat p “s”))
    (push (list ‘insert-patterned-convert-to-string (aref (car rfs) 0)) args))
    ((= (length (car rfs)) 2)
    (setq p (concat p (insert-patterned-convert-to-string (aref (car rfs) 1))))
    (push (aref (car rfs) 0) args))
    (t
    (error “I don’t understand the pattern %s” (insert-patterned-convert-to-string rfs)))))
    ((?i)
    (push ‘(insert-patterned-convert-to-string i) args)
    (setq pattern-tail (insert-patterned-string-cdr pattern-tail))
    (setq p (concat p “s”)))
    ((?j)
    (when (< nloops 2) (error "No j index — only %d loops" nloops))
    (push '(insert-patterned-convert-to-string j) args)
    (setq pattern-tail (insert-patterned-string-cdr pattern-tail))
    (setq p (concat p "s")))
    ((?k)
    (when (< nloops 3) (error "No k index — only %d loops" nloops))
    (push '(insert-patterned-convert-to-string k) args)
    (setq pattern-tail (insert-patterned-string-cdr pattern-tail))
    (setq p (concat p "s")))
    ((?l)
    (when (< nloops 4) (error "No l index — only %d loops" nloops))
    (push '(insert-patterned-convert-to-string l) args)
    (setq pattern-tail (insert-patterned-string-cdr pattern-tail))
    (setq p (concat p "s")))
    (?\(
    (setq rfs (read-from-string pattern-tail))
    (setq pattern-tail (substring pattern-tail (cdr rfs)))
    (setq p (concat p "s"))
    (push (list 'insert-patterned-convert-to-string (car rfs)) args))
    (t
    (setq p (concat p (substring pattern-tail 1 1)))
    (push 'i args)))

    )
    ; (insert-format "%s %s \n" (2str (concat p pattern-tail)) (2str (reverse args)))
    (list (concat p pattern-tail) (reverse args))
    ))

    (defun insert-patterned-read-string-memory (prompt memory-symbol)
    "Reads a string from the minibuffer, prompting with PROMPT.
    The default is the value of the variable MEMORY-SYMBOL.
    PROMPT should be a format string with a %s where PROMPT should go.
    The value entered will be stored in memory-symbol."
    (unless (boundp memory-symbol) (set memory-symbol ""))
    (let ((v (read-string (format prompt (eval memory-symbol)))))
    (if (string= v "") (setq v (eval memory-symbol)))
    (set memory-symbol v)
    v))

    (defun insert-patterned-try-to-eval (thing)
    "Tries to evaluate THING.
    If it succeeds, returns that value.
    Otherwise, returns THING itself."
    (condition-case ()
    (eval thing)
    (error thing)))

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>