unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* RFC: String interpolation
@ 2016-12-08  1:13 Clément Pit--Claudel
  2016-12-08  8:27 ` Andreas Schwab
                   ` (5 more replies)
  0 siblings, 6 replies; 86+ messages in thread
From: Clément Pit--Claudel @ 2016-12-08  1:13 UTC (permalink / raw)
  To: Emacs developers


[-- Attachment #1.1.1: Type: text/plain, Size: 2750 bytes --]

Hi all,

Many languages are gaining literal string interpolations features of late (the latest example is Python; see https://www.python.org/dev/peps/pep-0498/).  The TL;DR is that in Python 3.6 one can write this:

    value = 1.3
    print(f'The value is {value + 1:.2f}.')

This prints "The value is 2.30."  Plenty of other languages have similar features.

It would be nice if Emacs offered something similar out of the box. It could be a reader macro, but a plain macro would do just as well, and indeed it's relatively easy to code something up (I attached an example implementation).

With the attached fmt.el, you can write things like this:

    (let ((a 12) (b 15))
      (fmt "$a + $b = $(+ a b)"))
    ⇒ "12 + 15 = 27"

    (let ((password "letmein"))
      (fmt "The password is ${password}"))
    ⇒ "The password is letmein"

    (let ((n 25) (price-per-unit 10.5))
      (fmt "The price for $n units is €$[%.2f](* n price-per-unit)"))
    ⇒ "The price for 25 units is €262.50"

    (fmt "Here's a symbol: '${'a}; "
         "and a padded number: ‘$[%-6.3f]{1}’.")
    ⇒ "Here's a symbol: 'a; and a padded number: ‘1.000 ’."

    (fmt "Welcome to Emacs! "
         "Press $[where-is]{'help-with-tutorial} to open the tutorial.")
    ⇒ "Welcome to Emacs! Press C-h t to open the tutorial."

    (fmt "Today is $[date|%Y-%m-%d](current-time).")
    ⇒ "Today is 2016-12-07."

There's also an fmt-message macro that just calls ‘message’ on the interpolated string. The prototype that I attached supports a range of placeholders:

* $a, $a-b, $abc-123-def expand to the values of the corresponding variables
* ${a}, ${123}, ${[x y z]}, ${(+ 1 2)} expand to the value of the corresponding ELisp forms
* $(+ 1 2), $(buffer-file-name), $(current-column) are a shorthand for ${(…)}
* $[%.2f](+ 1 2) and $[%S]{'(1 2 3)} feed the result of evaluation through format, with the given specifier
* $[whereis]{…}' and $[date|%Y-%m-%d]{…} feed the result of evaluating "…" through a custom formatting function, with an optional format specifier
* $$ produces a literal $ sign

fmt expands the strings at compile time (so they must be constant strings).  For example, (fmt "$a + $b = $[%.2f](+ a b)") more or less expands to

    (concat (fmt--print a) " + " (fmt--print b) " = " (fmt--print (+ a b) 'format "%.2f"))

(fmt--print is a thin wrapper that sets print-depth and print-length).

I'd like some feedback on the following:

* Should this go into MELPA, ELPA, or core?
* What do we think of the syntax? An alternative to $[…]{…} would be ${…}%… (which is a bit trickier to parse, and makes it hard to have custom formatters).

Cheers,
Clément.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1.1.2: fmt.el --]
[-- Type: text/x-emacs-lisp; name="fmt.el", Size: 5277 bytes --]

;;; fmt.el --- Macro-based string interpolation library  -*- lexical-binding: t; -*-

;; Copyright (C) 2016  Clément Pit-Claudel

;; Author: Clément Pit-Claudel <clement.pitclaudel@live.com>
;; Keywords: convenience, extensions

;; This program 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 program 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:

;;

;;; Code:
(defconst fmt--sigil ?$)

;;; Printing utilities

(defun fmt--print (e &optional printer format-sequence)
  "Print E with PRINTER according to optional FORMAT-SEQUENCE.
With no FORMAT-SEQUENCE, use princ.  With no PRINTER, use FORMAT."
  (let ((print-quoted t)
        (print-length nil)
        (print-level nil))
    (if format-sequence
        (funcall (or printer 'format) format-sequence e)
      (prin1-to-string e t))))

;;; Specialized printers

(defun fmt--printers-str (_format-string v)
  "Format V as a string, but return \"\" if V is nil."
  (if v (format "%s" v) ""))

(defun fmt--printers-whereis (_format-string function)
  "Show a keybinding for FUNCTION."
  (substitute-command-keys (concat "\\[" (symbol-name function) "]")))

(defvar fmt--printers-alist '(("str" . fmt--printers-str)
                           ("date" . format-time-string)
                           ("where-is" . fmt--printers-whereis))
  "An alist mapping printer names to formatting functions.
Each formatting function should take a value and a format
sequence, and return a string.")

(defun fmt--get-printer (name)
  "Find printer for NAME."
  (or (cdr (assoc name fmt--printers-alist))
      (error "Unrecognized printer: %S" name)))

;;; Compiling format strings

(defun fmt--read-delimited ()
  "Read a string enclosed in a pair of delimiters."
  (let ((start (point)))
    (forward-list)
    (buffer-substring-no-properties (1+ start) (1- (point)))))

(defun fmt--read-format-sequence ()
  "Read a []-delimited format sequence, such as `[.2f]'."
  (when (eq (char-after) ?\[)
    (let ((str (fmt--read-delimited)))
      (if (string-match-p "^%" str)
          (list ''format str)
        (let* ((parts (split-string str "|")))
          (list (list 'quote (fmt--get-printer (car parts)))
                (mapconcat #'identity (cdr parts) "|")))))))

(defun fmt--read-braces ()
  "Read a {}-delimited form, such as `{12}'."
  (pcase-let* ((start (point))
               (expr (fmt--read-delimited))
               (`(,obj . ,count) (read-from-string expr)))
    (prog1 obj
      (unless (= count (length expr))
        (error "Expecting \"}\", but got %S instead"
               (buffer-substring (+ start count 1) (point-max)))))))

(defun fmt--read-var-name ()
  "Read an alphanumeric+dashes variable name, such as `ab0-cd'."
  (let ((start (point)))
    (if (> (skip-chars-forward "-[:alnum:]") 0)
        (intern (buffer-substring-no-properties start (point)))
      (error "Invalid fmt string when reaching %S"
             (buffer-substring (point) (point-max))))))

(defun fmt--read ()
  "Read one form, a lone sigil, or a variable, plus an optional format.
Returns a list (FORM PRINTER FORMAT-SEQUENCE)."
  (let* ((format-sequence (fmt--read-format-sequence))
         (next-char (char-after)))
    (apply #'list
           (cond
            ((equal next-char ?\() (read (current-buffer)))
            ((equal next-char ?\{) (fmt--read-braces))
            ((equal next-char fmt--sigil) (forward-char) (char-to-string fmt--sigil))
            (t (fmt--read-var-name)))
           format-sequence)))

(defun fmt--compile (expr)
  "Parse and compile a format expression EXPR."
  (with-temp-buffer
    (insert expr)
    (goto-char (point-min))
    (let ((chunks nil)
          (str-start (point))
          (sigil-str (char-to-string fmt--sigil)))
      (while (search-forward sigil-str nil t)
        (let ((str (buffer-substring-no-properties str-start (1- (point)))))
          (push str chunks)
          (push `(fmt--print ,@(fmt--read)) chunks)
          (setq str-start (point))))
      (push (buffer-substring-no-properties str-start (point-max)) chunks)
      (nreverse (seq-remove (lambda (x) (equal x "")) chunks)))))

;;; Public API

(defun fmt--1 (expr)
  "Compile a format expressions EXPR."
  (unless (stringp expr)
    (error "`fmt' requires a constant string"))
  (fmt--compile expr))

(defmacro fmt (&rest exprs)
  "Compile a sequence of format expressions EXPRS."
  (cons 'concat (apply #'append (mapcar #'fmt--1 exprs))))

(defmacro fmt-message (exprs)
  "Display one or more messages EXPRS.
EXPRS is compiled as a format expression."
  `(message "%s" (fmt ,@exprs)))

(provide 'fmt)
;;; fmt.el ends here

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 819 bytes --]

^ permalink raw reply	[flat|nested] 86+ messages in thread

end of thread, other threads:[~2016-12-14 17:01 UTC | newest]

Thread overview: 86+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-12-08  1:13 RFC: String interpolation Clément Pit--Claudel
2016-12-08  8:27 ` Andreas Schwab
2016-12-08  8:38   ` Joost Kremers
2016-12-08  9:05     ` Philippe Vaucher
2016-12-08 18:25   ` Clément Pit--Claudel
2016-12-08  9:02 ` Philippe Vaucher
2016-12-08 18:22   ` Clément Pit--Claudel
2016-12-08 14:43 ` Joost Kremers
2016-12-08 18:37   ` Clément Pit--Claudel
2016-12-08 20:04     ` Stefan Monnier
2016-12-09  8:57     ` Joost Kremers
2016-12-08 15:51 ` Drew Adams
2016-12-08 18:21   ` Clément Pit--Claudel
2016-12-08 20:38   ` Richard Stallman
     [not found]   ` <<E1cF5SK-0000HO-EL@fencepost.gnu.org>
2016-12-08 22:04     ` Drew Adams
2016-12-08 19:05 ` Stefan Monnier
2016-12-08 19:31   ` Clément Pit--Claudel
2016-12-08 21:31     ` Stefan Monnier
2016-12-10 16:01       ` Clément Pit--Claudel
2016-12-09 19:19 ` Ted Zlatanov
2016-12-09 22:45   ` Clément Pit--Claudel
2016-12-11  2:53     ` Ted Zlatanov
2016-12-11 18:46       ` Lars Ingebrigtsen
2016-12-11 18:56         ` Clément Pit--Claudel
2016-12-11 19:34           ` Lars Ingebrigtsen
2016-12-11 17:38     ` Stefan Monnier
2016-12-11 17:53       ` Clément Pit--Claudel
2016-12-10 14:11   ` Lars Ingebrigtsen
2016-12-10 14:25     ` Clément Pit--Claudel
2016-12-10 15:39       ` Lars Ingebrigtsen
2016-12-10 15:49         ` Clément Pit--Claudel
2016-12-10 16:01           ` Yuri Khan
2016-12-10 17:58             ` Clément Pit--Claudel
2016-12-11 17:47             ` Stefan Monnier
2016-12-11 19:31               ` Yuri Khan
2016-12-11 20:12                 ` Stefan Monnier
2016-12-11  9:35           ` Richard Stallman
2016-12-11 16:29             ` Clément Pit--Claudel
2016-12-11 18:42             ` Lars Ingebrigtsen
2016-12-12 19:41               ` Richard Stallman
2016-12-11 18:41           ` Lars Ingebrigtsen
2016-12-11 21:05             ` Helmut Eller
2016-12-11 21:26               ` Lars Ingebrigtsen
2016-12-12  2:39                 ` Stefan Monnier
2016-12-12  9:44                   ` Lars Ingebrigtsen
2016-12-12 13:03                     ` Stefan Monnier
2016-12-12 14:47                       ` Lars Ingebrigtsen
2016-12-12 15:04                         ` Stefan Monnier
2016-12-12 15:26                           ` Lars Ingebrigtsen
2016-12-12 17:05                             ` Stefan Monnier
2016-12-12 17:12                             ` Lars Ingebrigtsen
2016-12-12 17:23                               ` Stefan Monnier
2016-12-12 13:40                     ` Clément Pit--Claudel
2016-12-12 14:43                       ` Lars Ingebrigtsen
2016-12-12 14:54                         ` Clément Pit--Claudel
2016-12-12 17:14                           ` Stefan Monnier
2016-12-12 17:46                     ` Paul Eggert
2016-12-12 18:33                       ` Lars Ingebrigtsen
2016-12-12 19:51                       ` Ted Zlatanov
2016-12-12 20:41                         ` Paul Eggert
2016-12-12 23:40                           ` Paul Eggert
2016-12-13 17:42                             ` Richard Stallman
2016-12-13 18:05                               ` Paul Eggert
2016-12-13 23:55                                 ` Lars Ingebrigtsen
2016-12-14  0:07                                   ` Paul Eggert
2016-12-14  0:10                                     ` John Wiegley
2016-12-14  0:14                                       ` Lars Ingebrigtsen
2016-12-14  0:17                                       ` Paul Eggert
2016-12-14  0:26                                         ` John Wiegley
2016-12-14  1:48                                           ` Drew Adams
2016-12-14  2:48                                             ` Elias Mårtenson
2016-12-14  3:33                                               ` Drew Adams
2016-12-14  3:52                                                 ` Elias Mårtenson
2016-12-14 13:52                                             ` Lars Brinkhoff
2016-12-14 13:57                                               ` Lars Brinkhoff
2016-12-14  0:24                                       ` Drew Adams
2016-12-14  0:21                                     ` Clément Pit--Claudel
2016-12-14 14:10                                     ` Lars Ingebrigtsen
2016-12-14 17:00                                 ` Richard Stallman
2016-12-12 19:43                     ` Richard Stallman
2016-12-12 21:11                       ` Lars Ingebrigtsen
2016-12-13 17:41                         ` Richard Stallman
2016-12-13 17:43                           ` Lars Ingebrigtsen
2016-12-14 17:01                             ` Richard Stallman
2016-12-13  1:04                       ` Ted Zlatanov
2016-12-12 16:39                   ` Helmut Eller

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