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)))