[solved] String interpolation

Started by vetelko, July 30, 2017, 08:33:57 AM

Previous topic - Next topic

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?
newLISP v.10.7.6 64-bit on BSD IPv4/6 UTF-8 libffi

tomtoo

#1
Something like this?

> (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"
>

vetelko

#2
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:")



(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
newLISP v.10.7.6 64-bit on BSD IPv4/6 UTF-8 libffi

vetelko

#3
I'm new in (new)lisp, if someone can optimize it I would be glad.



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

newLISP v.10.7.6 64-bit on BSD IPv4/6 UTF-8 libffi

rickyboy

#4
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.


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


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


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

Next, I have a database of people.


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

Finally, the "punchline".


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


> (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!
(λx. x x) (λx. x x)

vetelko

#5
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.



<%
(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"



(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))
newLISP v.10.7.6 64-bit on BSD IPv4/6 UTF-8 libffi

rickyboy

#6
Quote from: "vetelko"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.



<%
(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.


(define (p str (sep ":"))
  (println
   (replace (string sep "([a-z]+)" sep)
            str
            (string (eval (sym $1)))
            0)))

Or, with the `ret` flag back in.


(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!
(λx. x x) (λx. x x)

vetelko

#7
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.
newLISP v.10.7.6 64-bit on BSD IPv4/6 UTF-8 libffi

varbanov

#8
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. :)

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