unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Tino Calancha <tino.calancha@gmail.com>
To: Emacs developers <emacs-devel@gnu.org>
Cc: Tino Calancha <tino.calancha@gmail.com>
Subject: New library num-base-converters
Date: Tue, 15 Aug 2017 02:26:18 +0900 (JST)	[thread overview]
Message-ID: <alpine.DEB.2.20.1708150225001.30658@calancha-pc> (raw)



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 <tino.calancha@gmail.com>
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 <tino.calancha@gmail.com>
+;; 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 <tino.calancha@gmail.com>
;; Maintainer: Tino Calancha <tino.calancha@gmail.com>
;; 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	other threads:[~2017-08-14 17:26 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-08-14 17:26 Tino Calancha [this message]
2017-08-14 17:48 ` New library num-base-converters Ted Zlatanov
2017-08-15  2:11   ` raman
2017-08-15  2:50     ` Tino Calancha
2017-08-15  5:25       ` Drew Adams
2017-08-15  6:35         ` Tino Calancha
2017-08-15  7:55 ` Stefan Monnier
2017-08-15  9:49   ` Tino Calancha
2017-08-15 15:24     ` Stefan Monnier
2017-08-15 13:40   ` Ted Zlatanov

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/emacs/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=alpine.DEB.2.20.1708150225001.30658@calancha-pc \
    --to=tino.calancha@gmail.com \
    --cc=emacs-devel@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).