From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!.POSTED!not-for-mail From: Tino Calancha Newsgroups: gmane.emacs.devel Subject: New library num-base-converters Date: Tue, 15 Aug 2017 02:26:18 +0900 (JST) Message-ID: NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: text/plain; format=flowed; charset=US-ASCII X-Trace: blaine.gmane.org 1502731604 29228 195.159.176.226 (14 Aug 2017 17:26:44 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Mon, 14 Aug 2017 17:26:44 +0000 (UTC) User-Agent: Alpine 2.20 (DEB 67 2015-01-07) Cc: Tino Calancha To: Emacs developers Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Mon Aug 14 19:26:36 2017 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by blaine.gmane.org with esmtp (Exim 4.84_2) (envelope-from ) id 1dhJ8N-00076U-LK for ged-emacs-devel@m.gmane.org; Mon, 14 Aug 2017 19:26:35 +0200 Original-Received: from localhost ([::1]:37009 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dhJ8U-00018B-3b for ged-emacs-devel@m.gmane.org; Mon, 14 Aug 2017 13:26:42 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:48875) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1dhJ8I-00016D-H5 for emacs-devel@gnu.org; Mon, 14 Aug 2017 13:26:33 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1dhJ8D-0006SE-Ee for emacs-devel@gnu.org; Mon, 14 Aug 2017 13:26:30 -0400 Original-Received: from mail-pg0-x233.google.com ([2607:f8b0:400e:c05::233]:36578) by eggs.gnu.org with esmtps (TLS1.0:RSA_AES_128_CBC_SHA1:16) (Exim 4.71) (envelope-from ) id 1dhJ8D-0006RE-5o for emacs-devel@gnu.org; Mon, 14 Aug 2017 13:26:25 -0400 Original-Received: by mail-pg0-x233.google.com with SMTP id i12so358741pgr.3 for ; Mon, 14 Aug 2017 10:26:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:date:to:cc:subject:message-id:user-agent:mime-version; bh=Xn+kFgepVPLEqIvXK7UkjC07Lgb500PVD66e/XD4rug=; b=l72X1wUykab/mfa10uCisH6FJyKb/MufJe9stgfEIJUpsHvOmolgDA6peJgxKWk2/W iMUjbo7zITsYTNsu/Y8BvYc1ZcdBiW3MTjUr4/4WK67AZa7bFXLDQgQx/iUXXWheZmVq YcxL7VmeXUXYMqZ17/D/SGqKu/r3iTvJS/D7nZA9VlLqmE04JPX7aVpAwKGss7NXkSK5 oaIZZLEiGvsyKgnrI7sLAELGSS8P4NgqJjeRVMccrEkCV27QjKBqgr4o6wtKcOMONXVv FyIWCSiRpSTFV40wc/pMtlfuEmcLIR1WcItikHFrZwKaC0KtOzJa+1GaqRWCBxJVPt1t NxPw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:date:to:cc:subject:message-id:user-agent :mime-version; bh=Xn+kFgepVPLEqIvXK7UkjC07Lgb500PVD66e/XD4rug=; b=RE/L4nwJt0pkrpg/B67WPZeplHsBnh2n1I4F+O8KJ6mUhjKqAH06+sD77otaV/txjB XCQB+MrCw1wn12LCF3vRhqIuLBQ1QoLdcSm3ZXpc9AbtG45DRmfM3ZxLy1o+P15AXRd6 wrm7oLMhysNQr9yYYXBZIt27lje4Exfw1aCDRYCYijjYNAb3lQNu566yI6B/21wEF/dI MQeB6uxvYY46jux4VF/N+0XDRX0/meCAnsYdvXYMteCUq5/6mBUiN1fyHv29bk5G9OUI 3BEKCZPSvwUIL9KpOBOlYQyyrdXDGI67H/4ueQID22gTJJ9U2PuvzKxYlHet2sdMl9A6 WX1Q== X-Gm-Message-State: AHYfb5gUDdtzCdtcPkspbKIp95yvbJPh18x5uLzmiJNE0co/XD23wjOY 2Ap7OZ6FVcwUFhGB X-Received: by 10.99.62.201 with SMTP id l192mr25046614pga.408.1502731582024; Mon, 14 Aug 2017 10:26:22 -0700 (PDT) Original-Received: from calancha-pc (170.224.128.101.dy.bbexcite.jp. [101.128.224.170]) by smtp.gmail.com with ESMTPSA id f88sm15072493pff.74.2017.08.14.10.26.20 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Mon, 14 Aug 2017 10:26:21 -0700 (PDT) X-Google-Original-From: Tino Calancha X-X-Sender: calancha@calancha-pc X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 2607:f8b0:400e:c05::233 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.org gmane.emacs.devel:217544 Archived-At: 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 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 +;; 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 . + +;;; 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 . + +;;; 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 ;; Maintainer: Tino Calancha ;; 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 . ;;; 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---