Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

string extrapolation in lisp

92 views
Skip to first unread message

Jim Newton

unread,
Jul 26, 2016, 6:01:05 AM7/26/16
to
Has anyone written a string extrapolation function similar to shell, or perl, or several other languages.
I can think of several ways to ALMOST do it, but I can't think of any really satisfactory semantics.
Here is an example.

(let ((a 100) (foo 200) (c-d-e "hello"))
(my-print (extrapolate "a=$a foo=$foo c-d-e=$c-d-e")))

The macro call (extrapolate "a=$a foo=$foo c-d-e=$c-d-e") should expand to something like
the following:

(format nil "a=~A foo=~A c-d-e=~A"
a foo c-d-e)

But it is not at all clear to me which package the symbols a, foo, and c-d-e, should come from in the macro expansion.

Is there a way to solve this so that such an extrapolate function would be useful?

Sebastian Christ

unread,
Jul 26, 2016, 7:13:28 AM7/26/16
to
Hi Jim,

I think CL-INTERPOL(http://weitz.de/cl-interpol/) does what you want.

(ql:quickload '(cl-interpol))
(cl-interpol:enable-interpol-syntax)
(let ((a 100) (foo 200) (c-d-e "hello"))
#?"a=$(a) foo=$(foo) c-d-e=$(c-d-e)")
;; "a=100 foo=200 c-d-e=hello"

Regards,
Sebastian

--
Sebastian (Rudolfo) Christ
http://rudolfochrist.github.io
GPG Fingerprint: 306D 8FD3 DFB6 4E44 5061
CE71 6407 D6F8 2AC5 55DD

Rob Warnock

unread,
Aug 10, 2016, 7:57:07 AM8/10/16
to
Jim Newton <jimka...@gmail.com> wrote:
+---------------
| The macro call (extrapolate "a=$a foo=$foo c-d-e=$c-d-e")
| should expand to something like the following:
|
| (format nil "a=~A foo=~A c-d-e=~A" a foo c-d-e)
+---------------

While Sebastian's suggestion of CL-INTERPOL (in a parallel reply)
solves the general problem you *asked*, if what you really want
is just to print variable names and values (or short sexprs & values)
then something simpler might do, e.g., the DBGV macro from my
personal UTILS package:

(defmacro dbgv ((&optional (where "Unknown Location"))
&rest forms)
`(progn
(format t "~&DBGV: @~a:~%" ',where)
,@(loop for form in forms
collect `(format t "~s = ~s~%" ',form ,form))))

Example usage:

> (let ((foo 136)
(bar 8))
(dbgv () foo bar (+ foo bar) (/ foo bar)))

DBGV: @Unknown Location:
FOO = 136
BAR = 8
(+ FOO BAR) = 144
(/ FOO BAR) = 17
NIL
>

When you have many of these scattered throughout your code,
the first arg (NIL above) can be used to say where the message
is coming from, e.g.:

> (dbgv (repl) (expt 2 32))

DBGV: @REPL:
(EXPT 2 32) = 4294967296
NIL
>


-Rob

-----
Rob Warnock <rp...@rpw3.org>
627 26th Avenue <http://rpw3.org/>
San Mateo, CA 94403

Jim Newton

unread,
Aug 10, 2016, 12:12:51 PM8/10/16
to
I have something similar called print-vals, here is the code:

(defmacro cl-user::print-vals (&rest args)
(let* ((var (gensym))
(content (loop :for arg :in args
:for n = 1 :then (1+ n)
:collect `(progn (format t "~2d/~d " ,n ,(length args))
(format t "~S~% --> " ',arg)
(finish-output)
(format t "~S~%" (setf ,var ,arg))))))
`(let ((,var nil))
,@content
(finish-output)
,var)))


CL-USER> (let ((foo 136)
(bar 8))
(print-vals foo bar (+ foo bar) (/ foo bar)))
1/4 FOO
--> 136
2/4 BAR
--> 8
3/4 (+ FOO BAR)
--> 144
4/4 (/ FOO BAR)
--> 17
17

Jim Newton

unread,
Aug 10, 2016, 12:14:43 PM8/10/16
to
Interesting thing about this approach seems to be that the symbols are determined at parse time, so *package* can be used to figure out which package to intern the symbol into.
That's clever.

Kaz Kylheku

unread,
Aug 10, 2016, 12:38:02 PM8/10/16
to
On 2016-08-10, Jim Newton <jimka...@gmail.com> wrote:
> Interesting thing about this approach seems to be that the symbols are
> determined at parse time, so *package* can be used to figure out which

Might you mean read time? "Parse" is ambiguous; a macro can parse
the argument structure, long after read time.

> package to intern the symbol into.

In fact, have you noticed how, if you don't pass a package
argument to intern, it uses the value of *package*?

A custom reader which extracts symbols within an interpolated
string literal simply has to assemble their names and
call intern. Well, not quite; it behooves such a read macro
to observe the *readtable-case*. If things are set up such that
FoO reads as the symbol "FOO", it should probably be that way in the
interpolating syntax also.

If the inner forms are delimited by something such as braces,
then all that the reader has to do is call read-delimited-list.

That is to say, given #?"whatever ${a} whatever", the ${a} part
could be handled by scanning ${, and then calling
(read-delimited-list #\} stream t). That takes care of the
*package* being used to intern the a token.

If the syntax is $(a), the (a) part can just be scanned by
a call to read, with recursive-p true.

Jim Newton

unread,
Aug 12, 2016, 5:44:30 AM8/12/16
to

> If the inner forms are delimited by something such as braces,
> then all that the reader has to do is call read-delimited-list.
>
> That is to say, given #?"whatever ${a} whatever", the ${a} part
> could be handled by scanning ${, and then calling
> (read-delimited-list #\} stream t). That takes care of the
> *package* being used to intern the a token.
>
> If the syntax is $(a), the (a) part can just be scanned by
> a call to read, with recursive-p true.

Yes, indeed. It's a very good solution in my opinion.
0 new messages