1
\$\begingroup\$

Every once in a while I need to convert numbers between bases. As far as I'm aware there are no existing commands that let you do this in Emacs, so I added some. These functions convert numbers at point and can either replace the number at point or simply display its value.

I'm much of a novice when it comes to Elisp, so I'd like to hear how I can improve this snippet. For instance, I bet the bounds handling can be made more robust; perhaps Emacs can already recognize number patterns.

More generally, I'd love to know how the code can be made more emacsy.

(defun simple-bounds-at-point (allowed-chars forward-regex)
  (save-excursion
    (skip-chars-backward allowed-chars)
    (if (looking-at forward-regex)
        (cons (point) (match-end 0))
      nil)))

(defun bounds-of-binary-at-point ()
  (simple-bounds-at-point "b01" "0b[01]+"))

(defun bounds-of-octal-at-point ()
  (simple-bounds-at-point "01234567" "0[0-7]+"))

(defun bounds-of-decimal-at-point ()
  (simple-bounds-at-point "0123456789" "[0-9]+"))

(defun bounds-of-hex-at-point ()
  (simple-bounds-at-point "x0123456789abcdefABCDEF" "0x[0-9a-fA-F]+"))

(put 'binary 'bounds-of-thing-at-point 'bounds-of-binary-at-point)
(put 'octal 'bounds-of-thing-at-point 'bounds-of-octal-at-point)
(put 'decimal 'bounds-of-thing-at-point 'bounds-of-decimal-at-point)
(put 'hex 'bounds-of-thing-at-point 'bounds-of-hex-at-point)

(defun format-binary (b)
  (let ((s ""))
    (while (> b 0)
      (setq s (concat (number-to-string (logand b 1)) s))
      (setq b (lsh b -1)))
    (if (string= "" s) "0" s)))

(unless (version< emacs-version "24")
  (ert-deftest format-binary ()
           (should (equal (format-binary 0) "0"))
           (should (equal (format-binary 1) "1"))
           (should (equal (format-binary 2) "10"))
           (should (equal (format-binary 3) "11"))
           (should (equal (format-binary 4) "100"))))

(defun radix-at-point ()
  (cond ((thing-at-point 'binary) 'binary)
        ((thing-at-point 'octal) 'octal)
        ((thing-at-point 'hex) 'hex)
        ((thing-at-point 'decimal) 'decimal)
        (t (error "NaN at point"))))

(defun numeric-radix (radix)
  (cond ((eq radix 'binary) 2)
        ((eq radix 'octal) 8)
        ((eq radix 'decimal) 10)
        ((eq radix 'hex) 16)))

(defun radix-prefix (radix)
  (cond ((eq radix 'binary) "0b")
        ((eq radix 'octal) "0")
        ((eq radix 'decimal) "")
        ((eq radix 'hex) "0x")))

(defun radix-format (radix)
  (cond ((eq radix 'octal) "%o")
        ((eq radix 'decimal) "%d")
        ((eq radix 'hex) "%x")))

(defun number-at-point ()
  (let* ((radix (radix-at-point))
         (skip-prefix (length (radix-prefix radix)))
         (numeric-radix (numeric-radix radix)))
    (string-to-number (substring (thing-at-point radix) skip-prefix nil)
                      numeric-radix)))

(defun format-number-as (number radix)
  (let ((prefix (radix-prefix radix)))
    (if (eq radix 'binary)
        (concat prefix (format-binary number))
      (concat prefix (format (radix-format radix) number)))))

(unless (version< emacs-version "24")
  (ert-deftest format-number-as ()
           (should (equal (format-number-as 255 'binary) "0b11111111"))
           (should (equal (format-number-as 255 'decimal) "255"))
           (should (equal (format-number-as 255 'octal) "0377"))
           (should (equal (format-number-as 255 'hex) "0xff"))))

(defun at-point-as (radix)
  (format-number-as (number-at-point) radix))

(defun at-point-as-binary ()
  (at-point-as 'binary))

(defun at-point-as-octal ()
  (at-point-as 'octal))

(defun at-point-as-decimal ()
  (at-point-as 'decimal))

(defun at-point-as-hex ()
  (at-point-as 'hex))

(defun as-binary ()
  "Display a binary, octal, decimal, or hexadecimal at point as binary"
  (interactive)
  (message (at-point-as-binary)))

(defun as-octal ()
  "Display a binary, octal, decimal, or hexadecimal at point as octal"
  (interactive)
  (message (at-point-as-octal)))

(defun as-decimal ()
  "Display a binary, octal, decimal, or hexadecimal at point as decimal"
  (interactive)
  (message (at-point-as-decimal)))

(defun as-hex ()
  "Display a binary, octal, decimal, or hexadecimal at point as hexadecimal"
  (interactive)
  (message (at-point-as-hex)))

(defun convert-number-at-point (as)
  (let ((bounds (bounds-of-thing-at-point (radix-at-point))))
    (delete-region (car bounds) (cdr bounds)))
  (insert as))

(defun to-binary ()
  "Convert a binary, octal, decimal, or hexadecimal at point to binary"
  (interactive)
  (convert-number-at-point (at-point-as-binary)))

(defun to-octal ()
  "Convert a binary, octal, decimal, or hexadecimal at point to octal"
  (interactive)
  (convert-number-at-point (at-point-as-octal)))

(defun to-decimal ()
  "Convert a binary, octal, decimal, or hexadecimal at point to decimal"
  (interactive)
  (convert-number-at-point (at-point-as-decimal)))

(defun to-hex ()
  "Convert a binary, octal, decimal, or hexadecimal at point to hexadecimal"
  (interactive)
  (convert-number-at-point (at-point-as-hex)))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.