Page 1 of 1

[solved] String interpolation

Posted: Sun Jul 30, 2017 3:33 pm
by vetelko
Hello guys,

is there some way how to replace placeholders in string with symbols values?
"value of one is :one: and value of two is :two:"
I want to replace :one: and :two: with values of symbols 'one and 'two if they exists. How to get symbol value if its name is available as string?

Re: String interpolation

Posted: Sun Jul 30, 2017 8:49 pm
by tomtoo
Something like this?

Code: Select all

> (set (sym "foo") "bar")
"bar"
> foo
"bar"
> (set 'a "one :two: three")
"one :two: three"
> a
"one :two: three"
> (replace ":two:" a foo)
"one bar three"
>

Re: String interpolation

Posted: Mon Jul 31, 2017 7:01 am
by vetelko
OK. It's like placeholders in function format but named so
instead of %s you are using named placeholders
The effect should be like in PHP where you write something like this
$one = 1;
$two = 2;
$str = "here $one and there $two";

So instead of
(println (format "here %d and there %d" one two))
one can use
(myprintln "here :one: and there :two:")

Code: Select all

(set 'one 1)
(set 'two 2)
(set 'str "here :one: and there :two:")
(set 'placeholders '())
(find-all {:([a-z]+):} str (push $1 placeholders -1))
(dolist (p placeholders)
    ;; pseudo
    replace string p in str with value of symbol of the same name if it exists
    in this case replace word "one" with 1 and word "two" with 2
(println str) # -> here 1 and there 2

Re: String interpolation

Posted: Tue Aug 01, 2017 8:36 am
by vetelko
I'm new in (new)lisp, if someone can optimize it I would be glad.

Code: Select all

(set 'name "John" 'age 37 'city "NY")

;; P replaces symbol name in string with its value, symbol name is enclosed
;; between colons (or choose your own), symbol must be defined
(define (P str (sep ":"))
    (set 'fields '())
    (find-all (format {%s([a-z0-9-]+)%s} sep sep) str (push $1 fields -1))
    (dolist (f fields)
        (if (set 'val (eval (sym f MAIN nil)))
            (replace (string sep f sep) str (string val))))
    (println str))

;; default call
(P ":name: lives in :city: and is :age: years old.")
;; -> John lives in NY and is 37 years old

;; custom separator
(P "!name! lives in !city! and is !age! years old." "!")
;; John lives in NY and is 37 years old.

;; custom separator, one symbol undefined
(P "~name~ lives in ~city~ and is ~blah~ years old." "~")
;; -> John lives in NY and is ~blah~ years old.


Re: [solved] String interpolation

Posted: Tue Aug 01, 2017 6:14 pm
by rickyboy
Hi vetelko!

Looks good! You didn't leave us much to work with / optimize. :)

I only have one comment, with doesn't really apply here because you are giving a short, illustrative example. However, in production code, I find it useful to separate the "printing code" from the "(answer) building code". In that light, I looked at your example in a different way, that is, I imagined a different context -- one in which I was looking at a process similar to MS Word's mail merge, where one has a template and then a "database" of "addresses" (or any other grouping of values) with which one wants to fill the template.

So, I wrote something very much like your P function, but without the println.

Code: Select all

(define (fill-template TEMPLATE VALUES)
  (replace ":([a-z]+):"
           TEMPLATE
           (if (lookup (sym $1) VALUES)
               (string $it)
               "<<<value does not exist>>>")
           0))
This function fills in the template TEMPLATE with values from VALUES.

TEMPLATE is a string containing "slots" (to fill in) denoted by symbol names surrounded by the colon character ':', e.g., ":name: is at :place:." (That is, it is in exactly the same format that you proposed.)

VALUES is an alist which associates those symbols (sans the colons) to corresponding values, e.g., '((name "Tom") (place "home")). This part is slightly different from your application, but you will soon see why I chose this below.

So, here is an example call, to get a feel for it.

Code: Select all

> (fill-template ":name: is at :place:." '((name "Tom") (place "home")))
"Tom is at home."
See? Very much like your P function.

Now, here's how the "mail merge"-type application works. First, I have a template. I proudly steal yours. :)

Code: Select all

(define *template* ":name: lives in :city: and is :age: years old.")
Next, I have a database of people.

Code: Select all

(define *people*
  '(((name "John")
     (age  37)
     (city "NY"))
    ((name "Giorgos")
     (age  25)
     (city "Athens"))
    ((name "Elena")
     (age  43)
     (city "Amsterdam"))))
Finally, the "punchline".

Code: Select all

> (map (curry fill-template *template*) *people*)
("John lives in NY and is 37 years old." "Giorgos lives in Athens and is 25 years old." 
 "Elena lives in Amsterdam and is 43 years old.")
Or, if you want println.

Code: Select all

> (dolist (p *people*) (println (fill-template *template* p)))
John lives in NY and is 37 years old.
Giorgos lives in Athens and is 25 years old.
Elena lives in Amsterdam and is 43 years old.
"Elena lives in Amsterdam and is 43 years old."
Thanks for sharing your solution! This was a fun break for me today. All the best to you!

Re: [solved] String interpolation

Posted: Tue Aug 01, 2017 7:49 pm
by vetelko
Hello, rickyboy.

Thank you for your long answer. I like the "curry" trick :) I still don't know/use all newlisp goodies. I'm playing with newlisp http server and I wanted to use such a function in my html view templates, this is why function prints by default. Now it looks cleaner for me than println/string combinations.

Code: Select all

<%
(dolist (m lst)
    (p {<div class="member"><b>:m:</b></div>}))
%>
For now, inspired by you, I changed the function definition so you can choose if you want printing or returning. It is solved with additional argument "ret"

Code: Select all

(define (P str (ret nil) (sep ":"))
    (set 'fields '())
    (find-all (format {%s([a-z0-9-]+)%s} sep sep) str (push $1 fields -1))
    (dolist (f fields)
        (if (set 'val (eval (sym f MAIN nil)))
            (replace (string sep f sep) str (string val))))
    (if (not ret)
        (println str)
        str))

Re: [solved] String interpolation

Posted: Thu Aug 03, 2017 4:03 am
by rickyboy
vetelko wrote:I'm playing with newlisp http server and I wanted to use such a function in my html view templates, this is why function prints by default. Now it looks cleaner for me than println/string combinations.

Code: Select all

<%
(dolist (m lst)
    (p {<div class="member"><b>:m:</b></div>}))
%>
Ah, I see what you are doing. Yes, you are right: make it simple and just print it.

In that case, I'd go with the definition of `p` as the following.

Code: Select all

(define (p str (sep ":"))
  (println
   (replace (string sep "([a-z]+)" sep)
            str
            (string (eval (sym $1)))
            0)))
Or, with the `ret` flag back in.

Code: Select all

(define (p str (ret nil) (sep ":"))
  ((if ret begin println)
   (replace (string sep "([a-z]+)" sep)
            str
            (string (eval (sym $1)))
            0)))
I'm just playing around here -- having fun. Really, you did an excellent job in the first place. Cheers!

Re: [solved] String interpolation

Posted: Fri Aug 04, 2017 5:51 pm
by vetelko
Thanks for your version rickyboy. Things happen when I don't know the language enough. Knowing replace can use regexp one can avoid find-all :) Cool.

Re: [solved] String interpolation

Posted: Fri Aug 18, 2017 11:00 am
by varbanov
Hi guys,

Sorry for posting on a solved task, but I just can't resist temptation to show my context version. :) No regular expresions, no (explicit) search/replace. :)

Code: Select all

(define *people*
      '((("name" "John")
         ("age"  37)
         ("city" "NY"))
        (("name" "Giorgos")
         ("age"  25)
         ("city" "Athens"))
        (("name" "Elena")
         ("age"  43)
         ("city" "Amsterdam"))))

(define *template* ":name: lives in :city: and is :age: years old.")

(setq parsed-template (parse *template* ":"))

(define data:data)     ; defines context to hold one person data

(dolist (p *people*)
	(data p)
	(dolist (str parsed-template) 
		(print (or (data str) str)))
	(println))
Yours,
s.v.