-
-
Notifications
You must be signed in to change notification settings - Fork 55
String interpolation #231
Copy link
Copy link
Closed
Description
Below is an adapted version of strint, from core.incubator.
I changed it to not act different wrt/ single and composite values since that didn't seem necessary at all to me.
We could add this to nbb.string, or maybe babashka.string (and also expose it in bb).
EDIT:
Other languages have ${...} and not having a special case for the composite case might make more sense:
${ (+ 1 2 3) }.
since it's fairly common to want to do this: ${ name }s.
This also aligns with JS:
> `Hello $x`
"Hello $x"
> `Hello ${x}`
"Hello 123"
;;; strint.clj -- String interpolation for Clojure
;; originally proposed/published at http://cemerick.com/2009/12/04/string-interpolation-in-clojure/
;; by Chas Emerick <cemerick@snowtide.com>
;; December 4, 2009
;; Copyright (c) Chas Emerick, 2009. All rights reserved. The use
;; and distribution terms for this software are covered by the Eclipse
;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
;; which can be found in the file epl-v10.html at the root of this
;; distribution. By using this software in any fashion, you are
;; agreeing to be bound by the terms of this license. You must not
;; remove this notice, or any other, from this software.
(ns
^{:author "Chas Emerick",
:doc "Compile-time string interpolation for Clojure."}
strint)
(defn- silent-read
"Attempts to clojure.core/read a single form from the provided String, returning
a vector containing the read form and a String containing the unread remainder
of the provided String. Returns nil if no valid form can be read from the
head of the String."
[s]
(try
(let [r (-> s java.io.StringReader. java.io.PushbackReader.
;; currently needed for bb
clojure.lang.LineNumberingPushbackReader.)]
[(read r) (slurp r)])
(catch Exception e (prn e)))) ; this indicates an invalid form -- the head of s is just string data
(defn- interpolate
"Yields a seq of Strings and read forms."
([s atom?]
(lazy-seq
(if-let [[form rest] (silent-read (subs s (if atom? 2 1)))]
(cons form (interpolate (if atom? (subs rest 1) rest)))
(cons (subs s 0 2) (interpolate (subs s 2))))))
([^String s]
(if-let [start (->> ["~"]
(map #(.indexOf s ^String %))
(remove #(== -1 %))
sort
first)]
(lazy-seq (cons
(subs s 0 start)
(interpolate (subs s start) false)))
[s])))
(defmacro <<
"Accepts one or more strings; emits a `str` invocation that concatenates
the string data and evaluated expressions contained within that argument.
Evaluation is controlled using ~{} and ~() forms. The former is used for
simple value replacement using clojure.core/str; the latter can be used to
embed the results of arbitrary function invocation into the produced string.
Examples:
user=> (def v 30.5)
#'user/v
user=> (<< \"This trial required ~{v}ml of solution.\")
\"This trial required 30.5ml of solution.\"
user=> (<< \"There are ~(int v) days in November.\")
\"There are 30 days in November.\"
user=> (def m {:a [1 2 3]})
#'user/m
user=> (<< \"The total for your order is $~(->> m :a (apply +)).\")
\"The total for your order is $6.\"
user=> (<< \"Just split a long interpolated string up into ~(-> m :a (get 0)), \"
\"~(-> m :a (get 1)), or even ~(-> m :a (get 2)) separate strings \"
\"if you don't want a << expression to end up being e.g. ~(* 4 (int v)) \"
\"columns wide.\")
\"Just split a long interpolated string up into 1, 2, or even 3 separate strings if you don't want a << expression to end up being e.g. 120 columns wide.\"
Note that quotes surrounding string literals within ~() forms must be
escaped."
[& strings]
`(str ~@(interpolate (apply str strings))))
;; escape ~
(prn (<< "~\\~"))
(prn
(let [x 1 y 2]
(<< "x: ~x, y: ~y, z: ~(+ 1 2 3)")))
;; "x: 1, y: 2, z: 6"
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels