emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

New library num-base-converters


From: Tino Calancha
Subject: New library num-base-converters
Date: Tue, 15 Aug 2017 02:26:18 +0900 (JST)
User-agent: Alpine 2.20 (DEB 67 2015-01-07)



Hi,

a simple library providing base converters for integers.

Convert integers between bases 2, 8, 10 and 16 is
a common task in the engineering and science applications.

I've being using for years several bash scripts calling `bc';
having a library written in elisp sounds like a better idea: it
will work regardless on the OS.

I think this library have its place in both, the core and ELPA.
What do you think?

;;; For Emacs master branch:
--8<-----------------------------cut here---------------start------------->8---
commit d00f1fc6a315e65d28f5212731f537ea9fb82050
Author: Tino Calancha <address@hidden>
Date:   Tue Aug 15 02:09:38 2017 +0900

    New library 'num-base-converters'

    * lisp/num-base-converters.el: New file.
    * test/lisp/num-base-converters-tests.el: Add test suite.
    * etc/NEWS (New Modes and Packages in Emacs 26.1):
    Announce it.

diff --git a/etc/NEWS b/etc/NEWS
index 3f38153048..4ec6671f1f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1093,6 +1093,9 @@ processes on exit.

 * New Modes and Packages in Emacs 26.1

+** New library num-base-converters' to convert integers between
+different bases.
+
 ** New Elisp data-structure library 'radix-tree'.

 ** New library 'xdg' with utilities for some XDG standards and specs.
diff --git a/lisp/num-base-converters.el b/lisp/num-base-converters.el
new file mode 100644
index 0000000000..dd31d13d2b
--- /dev/null
+++ b/lisp/num-base-converters.el
@@ -0,0 +1,152 @@
+;;; num-base-converters.el --- Convert integers between different numeric 
bases  -*- lexical-binding: t -*-
+
+;; Copyright (C) 2017 Free Software Foundation, Inc.
+
+;; Author: Tino Calancha <address@hidden>
+;; Keywords: convenience, numbers, converters, tools
+;; Created: Tue Aug 15 02:04:55 JST 2017
+;; Version: 0.1
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+ +;; This library defines the command `nbc-number-base-converter' to
+;; convert a given integer in a numeric base to a different base.
+;;
+;; For instance, 10 in hexadecimal is 'A':
+;; (nbc-number-base-converter "10" 10 16)
+;; => "A"
+;;
+;; In addition, this file adds the following commands to convert
+;; between the most common bases (2, 8, 10, 16):
+;; `nbc-hex2dec', `nbc-hex2oct', `nbc-hex2bin'
+;; `nbc-dec2hex', `nbc-dec2oct', `nbc-dec2bin'
+;; `nbc-oct2hex', `nbc-oct2dec', `nbc-oct2bin'
+;; `nbc-bin2hex', `nbc-bin2dec', `nbc-bin2oct'.
+
+;;; Code:
+ +
+(require 'calc-bin)
+(eval-when-compile (require 'rx))
+
+(defgroup num-base-converters nil
+  "Convert integers between different numeric bases."
+  :group 'nbc)
+
+(defcustom nbc-define-aliases nil
+  "If non-nil, create aliases without prefix 'nbc' for the converters."
+  :type 'boolean
+  :group 'nbc)
+
+ +
+(defun nbc-number-base-converter (num base-in base-out)
+  "Translate NUM, a string representing an integer, to a different base.
+BASE-IN, an integer, is the basis of the input NUM.
+BASE-OUT, an integer, is the basis to display NUM."
+  (interactive
+   (let ((num (read-string "Number: "))
+         (base-in (read-number "Base input: "))
+         (base-out (read-number "Base output: ")))
+     (list num base-in base-out)))
+  (unless (stringp num)
+    (signal 'wrong-type-argument (list 'stringp num)))
+  (unless (and (>= base-in 2) (<= base-in 36) (>= base-out 2) (<= base-out 36))
+    (user-error "Base `b' must satisfy 2 <= b <= 36: base-in `%d' base-out 
`%d'"
+                base-in base-out))
+  (let* ((case-fold-search nil)
+         (input (progn
+                  (pcase num ; Drop base info from NUMB.
+                    ((rx (and string-start
+                              (let _u (or "b" "0x" "o")
+                                   (let v (one-or-more not-newline)) 
string-end)))
+                     (setq num v))
+                    ((rx (and string-start "#"
+                              (let _u (or "b" "x" "o" (and (one-or-more digit) 
"r")))
+                              (let v (one-or-more not-newline)) string-end))
+                     (setq num v)))
+                  (condition-case nil
+                      ;; Translate to canonical syntaxis: #(base)r(number).
+                      (read (format "#%dr%s" base-in num))
+                       (invalid-read-syntax
+                     (user-error "Wrong input: `%s' for base `%s'"
+                                 num base-in))))))
+    (condition-case nil
+        (let* ((calc-number-radix base-out)
+               (output (math-format-radix input)))
+          (pcase output
+            ((rx (and string-start
+                      (let _u (zero-or-one (and (zero-or-more digit) "#"))
+                           (let v (and (one-or-more not-newline) 
string-end)))))
+             (setq output v))) ; Drop base info from OUTPUT.
+          (message "%s base %s = %s base %s" num base-in output base-out)
+          output)
+      (wrong-type-argument
+       (user-error "Wrong input: `%s' for base `%s'" num base-in)))))
+
+ +;;; Add translatros for the most common basis: decimal, hexadecimal,
+;;  octal and binary.
+(eval-when-compile
+  (defmacro nbc--create-converters-1 ()
+    (let ((bases (list "hex" "dec" "oct" "bin"))
+          forms)
+      (dolist (base-out bases)
+        (dolist (base-in bases)
+          (if (equal base-out base-in)
+              nil
+            (push `(nbc--create-command ,base-in ,base-out) forms))))
+      `(progn ,@forms)))
+
+  (defmacro nbc--create-command (base-in base-out)
+    (let* ((input-fn
+            (lambda (x)
+              (pcase x
+                (`"hex" (cons "hexadecimal" 16))
+                (`"dec" (cons "decimal" 10))
+                (`"oct" (cons "octal" 8))
+                (`"bin" (cons "binary" 2)))))
+           (in-lst (funcall input-fn base-in))
+           (base-in-name (car in-lst))
+           (out-lst (funcall input-fn base-out))
+           (func-name (format "nbc-%s2%s" base-in
+                              (substring (car out-lst) 0 3)))
+           (prefix-len (length "nbc-"))
+           (docstring (format "Translate NUM, a string, from %s to %s."
+                              (car in-lst) (car out-lst)))
+           (ispec (format "sNumber in %s: " (car in-lst))))
+      `(progn
+         (when (bound-and-true-p nbc-define-aliases)
+           (defalias (intern ,(substring func-name prefix-len))
+             (intern ,func-name)))
+         (defun ,(intern func-name) ,(list 'num)
+           ,docstring (interactive ,ispec)
+           (let ((res (nbc-number-base-converter
+                       num ,(cdr in-lst) ,(cdr out-lst))))
+             (message "%s %s = %s %s"
+                      num ,base-in-name res ,(car out-lst))
+             res))))))
+
+(defun nbc--create-converters ()
+  "Create converters between the bases 2, 8, 10 and 16."
+  (nbc--create-converters-1))
+
+(nbc--create-converters)
+
+(provide 'num-base-converters)
+;;; num-base-converters.el ends here
diff --git a/test/lisp/num-base-converters-tests.el 
b/test/lisp/num-base-converters-tests.el
new file mode 100644
index 0000000000..c6ac2912b9
--- /dev/null
+++ b/test/lisp/num-base-converters-tests.el
@@ -0,0 +1,86 @@
+;;; num-base-converters-tests.el --- Test suite for num-base-converters. -*- 
lexical-binding: t -*-
+
+;; Copyright (C) 2017 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Code:
+(require 'ert)
+(require 'num-base-converters)
+
+
+(ert-deftest nbc-test-converters ()
+  ;; Input is case-insensitive.
+  (dolist (x (list "a" "A"))
+    (should (equal "10" (nbc-hex2dec x)))
+    (should (equal "12" (nbc-hex2oct x)))
+    (should (equal "1010" (nbc-hex2bin x))))
+  ;;
+  (should (equal "A" (nbc-dec2hex "10")))
+  (should (equal "12" (nbc-dec2oct "10")))
+  (should (equal "1010" (nbc-dec2bin "10")))
+  ;;
+  (should (equal "3F" (nbc-oct2hex "77")))
+  (should (equal "63" (nbc-oct2dec "77")))
+  (should (equal "111111" (nbc-oct2bin "77")))
+  ;;
+  (should (equal "A" (nbc-bin2hex "1010")))
+  (should (equal "10" (nbc-bin2dec "1010")))
+  (should (equal "12" (nbc-bin2oct "1010"))))
+
+(ert-deftest nbc-test-nbc-number-base-converter ()
+  ;; Bases `b' must be: 2 <= b <= 36:
+  (should-error (nbc-number-base-converter "10" 37 10))
+  (should-error (nbc-number-base-converter "10" 10 37))
+  (should-error (nbc-number-base-converter "10" 10 1))
+  (should-error (nbc-number-base-converter "10" 1 10))
+  ;; Invalid input:
+  (should-error (nbc-number-base-converter "1a" 8 10))
+  (should-error (nbc-number-base-converter "az" 16 10))
+  (should-error (nbc-number-base-converter "3" 2 10))
+  (should-error (nbc-number-base-converter "z" 10 8))
+  ;;
+  (should (equal "8" (nbc-number-base-converter "10" 8 16)))
+  (should (equal "10" (nbc-number-base-converter "8" 16 8)))
+  (should (equal "12" (nbc-number-base-converter "10" 10 8)))
+  (should (equal "10" (nbc-number-base-converter "12" 8 10)))
+  (should (equal "1000" (nbc-number-base-converter "10" 8 2)))
+  (should (equal "10" (nbc-number-base-converter "1000" 2 8)))
+  ;;
+  (should (equal "10" (nbc-number-base-converter "a" 16 10)))
+  (should (equal "10" (nbc-number-base-converter "A" 16 10)))
+  (should (equal "A" (nbc-number-base-converter "10" 10 16)))
+  (should (equal "1010" (nbc-number-base-converter "a" 16 2)))
+  (should (equal "1010" (nbc-number-base-converter "A" 16 2)))
+  (should (equal "A" (nbc-number-base-converter "1010" 2 16)))
+  ;;
+  (should (equal "1010" (nbc-number-base-converter "10" 10 2)))
+  (should (equal "10" (nbc-number-base-converter "1010" 2 10))))
+
+(ert-deftest nbc-test-nbc-number-base-converter ()
+  (let (nbc-define-aliases)
+    (nbc--create-converters)
+    (should (fboundp 'nbc-oct2dec))
+    (should-not (fboundp 'oct2dec))
+    (setq nbc-define-aliases t)
+    ;; It should create the aliases, i.e. `oct2dec' etc.
+    (nbc--create-converters)
+    (should (fboundp 'oct2dec))))
+
+
+(provide 'num-base-converters-tests)
+
+;; num-base-converters-tests.el ends here

--8<-----------------------------cut here---------------end--------------->8---
In GNU Emacs 26.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.22.11)
 of 2017-08-14
Repository revision: 5ba4c7d16b800864fa14b8a981e33f6aa6fa94d6


;;; For Elpa:
--8<-----------------------------cut here---------------start------------->8---
;;; num-base-converters.el --- Convert integers between different numeric bases 
 -*- lexical-binding: t -*-

;; Copyright (C) 2017 Free Software Foundation, Inc.

;; Author: Tino Calancha <address@hidden>
;; Maintainer: Tino Calancha <address@hidden>
;; Keywords: convenience, numbers, converters, tools

;; Created: Tue Aug 15 02:04:55 JST 2017
;; Package-Requires: ((emacs "24.4"))
;; Version: 0.1

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.


;; This file is part of GNU Emacs.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This library defines the command `nbc-number-base-converter' to
;; convert a given integer in a numeric base to a different base.
;;
;; For instance, 10 in hexadecimal is 'A':
;; (nbc-number-base-converter "10" 10 16)
;; => "A"
;;
;; In addition, this file adds the following commands to convert
;; between the most common bases (2, 8, 10, 16):
;; `nbc-hex2dec', `nbc-hex2oct', `nbc-hex2bin'
;; `nbc-dec2hex', `nbc-dec2oct', `nbc-dec2bin'
;; `nbc-oct2hex', `nbc-oct2dec', `nbc-oct2bin'
;; `nbc-bin2hex', `nbc-bin2dec', `nbc-bin2oct'.

;;; Code:


(require 'calc-bin)

(defgroup num-base-converters nil
  "Convert integers between different numeric bases."
  :group 'nbc)

(defcustom nbc-define-aliases nil
  "If non-nil, create aliases without prefix 'nbc' for the converters."
  :type 'boolean
  :group 'nbc)



(defun nbc-number-base-converter (num base-in base-out)
  "Translate NUM, a string representing an integer, to a different base.
BASE-IN, an integer, is the basis of the input NUM.
BASE-OUT, an integer, is the basis to display NUM."
  (interactive
   (let ((num (read-string "Number: "))
         (base-in (read-number "Base input: "))
         (base-out (read-number "Base output: ")))
     (list num base-in base-out)))
  (unless (stringp num)
    (signal 'wrong-type-argument (list 'stringp num)))
  (unless (and (>= base-in 2) (<= base-in 36) (>= base-out 2) (<= base-out 36))
    (error "Base `b' must satisfy 2 <= b <= 36: base-in `%d' base-out `%d'"
           base-in base-out))
  (let* ((case-fold-search nil)
         (input (progn
                  ;; Drop base info from NUMB.
                  (cond ((string-match "\\`\\(b\\|0x\\|o\\)\\(.+\\)\\'" num)
                         (setq num (match-string 2 num)))
                        ((string-match 
"\\`#\\(b\\|x\\|o\\|[0-9]+r\\)\\(.+\\)\\'" num)
                         (setq num (match-string 2 num))))
                  (condition-case nil
                      ;; Translate to canonical syntaxis: #(base)r(number).
                      (read (format "#%dr%s" base-in num))
                    (invalid-read-syntax
                     (error "Wrong input: `%s' for base `%s'" num base-in))))))
    (condition-case nil
        (let* ((regexp "\\`\\([0-9]*#\\)?\\(.+\\'\\)")
               (calc-number-radix base-out)
               (output (math-format-radix input)))
          (when (string-match regexp output) ; Drop base info from OUTPUT.
            (setq output (match-string-no-properties 2 output)))
          (message "%s base %s = %s base %s" num base-in output base-out)
          output)
      (wrong-type-argument
       (error "Wrong input: `%s' for base `%s'" num base-in)))))


;;; Add translatros for the most common basis: decimal, hexadecimal,
;;  octal and binary.
(eval-when-compile
  (defmacro nbc--create-converters-1 ()
    (let ((bases (list "hex" "dec" "oct" "bin"))
          forms)
      (dolist (base-out bases)
        (dolist (base-in bases)
          (if (equal base-out base-in)
              nil
            (push `(nbc--create-command ,base-in ,base-out) forms))))
      `(progn ,@forms)))

  (defmacro nbc--create-command (base-in base-out)
    (let* ((input-fn
            (lambda (x)
              (pcase x
                (`"hex" (cons "hexadecimal" 16))
                (`"dec" (cons "decimal" 10))
                (`"oct" (cons "octal" 8))
                (`"bin" (cons "binary" 2)))))
           (in-lst (funcall input-fn base-in))
           (base-in-name (car in-lst))
           (out-lst (funcall input-fn base-out))
           (func-name (format "nbc-%s2%s" base-in
                              (substring (car out-lst) 0 3)))
           (prefix-len (length "nbc-"))
           (docstring (format "Translate NUM, a string, from %s to %s."
                              (car in-lst) (car out-lst)))
           (ispec (format "sNumber in %s: " (car in-lst))))
      `(progn
         (when (bound-and-true-p nbc-define-aliases)
           (defalias (intern ,(substring func-name prefix-len))
             (intern ,func-name)))
         (defun ,(intern func-name) ,(list 'num)
           ,docstring (interactive ,ispec)
           (let ((res (nbc-number-base-converter
                       num ,(cdr in-lst) ,(cdr out-lst))))
             (message "%s %s = %s %s"
                      num ,base-in-name res ,(car out-lst))
             res))))))

(defun nbc--create-converters ()
  "Create converters between the bases 2, 8, 10 and 16."
  (nbc--create-converters-1))

(nbc--create-converters)

(provide 'num-base-converters)
;;; num-base-converters.el ends here

--8<-----------------------------cut here---------------end--------------->8---



reply via email to

[Prev in Thread] Current Thread [Next in Thread]