Hello Quizzers,
I'm a newbie in Lisp (have already finished the ANSI Common Lisp book, made some tries with programming as well, and I'm searching the answer to this question I have : could it be possible to "enhance" the reader in order to have in lisp "enhanced strings".
That is : by just doing something once at the beginning of a lisp file (some call, ...), being able to have all the strings replaced by another string (or another form).
Something like this :
;;; beginning of file
(enhanced-strings:start)
... ...
(defun some-function (x y) "This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=${x}, y =${y}")
You see what I mean ....
This could be an interesting quiz, but first of all, will an ANSI compliant implementation let redefine the reader to alter the meaning of strings ?
I know I can add macro characters, play with macro character dispatching functions, ... but what about "overriding" the reader to intercept strings and change them, as for the above example, with :
(format t "This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=~a, y =~a") x y)
thanks in advance for your answers, both on feasibility and/or complete answers to this "unofficial" quiz ;-)
And oh, if I definitely bothered you by sending this message on the wrong list, please let me know what would be the most natural list/newsgroup to post this message on ?
Thanks in advance,
A lisp beginner,
Laurent PETIT writes:
Hello Quizzers,
I'm a newbie in Lisp (have already finished the ANSI Common Lisp book, made some tries with programming as well, and I'm searching the answer to this question I have : could it be possible to "enhance" the reader in order to have in lisp "enhanced strings".
That is : by just doing something once at the beginning of a lisp file (some call, ...), being able to have all the strings replaced by another string (or another form).
Something like this :
;;; beginning of file
(enhanced-strings:start)
... ...
(defun some-function (x y) "This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=${x}, y =${y}")
You see what I mean ....
This could be an interesting quiz,
No, it's trivial.
(defpackage "ENHANCED-STRINGS" (:USE "COMMON-LISP") (:EXPORT "START"))
(in-package "ENHANCED-STRINGS")
(defun parse-enhanced-string (string) (loop :with chunks = '() :with args = '() :with start = 0 :for pos = (search "${" string :start2 start) :for end = (and pos (search "}" string :start2 pos)) :while end :do (progn (push (subseq string start pos) chunks) (multiple-value-bind (form next) (read-from-string string t nil :start (+ 2 pos) :end end) (loop :while (and (< next end) (member (aref string next) '(#\space #\newline))) :do (incf next)) (unless (= next end) (error "Junk in ~S" (subseq string pos end))) (push form args)) (setf start (1+ end))) :finally (progn (push (subseq string start) chunks) (return (cons (format nil "~{~A~^~~A~}" (nreverse chunks)) (nreverse args))))))
(defun reader-macro--enhanced-string (stream dblquote) (let ((*readtable* (copy-readtable nil))) (unread-char dblquote stream) `(format nil ,@(parse-enhanced-string (read stream t nil t)))))
(defun start () (set-macro-character #" (function reader-macro--enhanced-string)))
(start) '"This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=${(* 42 x)}, y =${y}" --> (format nil "This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=~A, y =~A" (* 42 x) y)
See also: http://www.cliki.net/cl-interpol
but first of all, will an ANSI compliant implementation let redefine the reader to alter the meaning of strings ?
Yes.
I know I can add macro characters, play with macro character dispatching functions, ... but what about "overriding" the reader to intercept strings and change them, as for the above example, with :
What about SET-MACRO-CHARACTER and SET-DISPATCH-MACRO-CHARACTER ? Do you need instructions on how to search CLHS?
Pascal Bourguignon writes:
(defun parse-enhanced-string (string) [...] (start) '"This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=${(* 42 x)}, y =${y}" --> (format nil "This returned string is an enhanced string and the value of x and y will be replaced 'a la ruby' : x=~A, y =~A" (* 42 x) y)
There's a little problem with my function, it doesn't escape #~ in the string. This is left as an exercise for the reader...
(quote "~ ho ho ~ x=#{x} ~") --> (format nil "~~ ho ho ~~ x=~A ~~" x)
Hello,
2006/11/28, Pascal Bourguignon pjb@informatimago.com:
[...] No, it's trivial.
Thank you very much for your answer, and even for the solution !!
See also: http://www.cliki.net/cl-interpol
Thanks for the link !
[...]
I know I can add macro characters, play with macro character dispatching functions, ... but what about "overriding" the reader to intercept
strings
and change them, as for the above example, with :
What about SET-MACRO-CHARACTER and SET-DISPATCH-MACRO-CHARACTER ? Do you need instructions on how to search CLHS?
Well, I'm not sure this one is ironic or serious ? ;-)
I already know about Common Lisp Hyper Spec. Still not totally familiar with it, but making progress.
What has blocked me was the fact that I didn't know if it was possible to override certain macro characters.
Nevertheless, if you have instructions that would make me more productive while searching CLHS, I'll receive them with pleasure :-)
Laurent PETIT writes:
Hello,
2006/11/28, Pascal Bourguignon pjb@informatimago.com:
[...] No, it's trivial.
Thank you very much for your answer, and even for the solution !!
See also: http://www.cliki.net/cl-interpol
Thanks for the link !
[...]
I know I can add macro characters, play with macro character dispatching functions, ... but what about "overriding" the reader to intercept
strings
and change them, as for the above example, with :
What about SET-MACRO-CHARACTER and SET-DISPATCH-MACRO-CHARACTER ? Do you need instructions on how to search CLHS?
Well, I'm not sure this one is ironic or serious ? ;-)
I already know about Common Lisp Hyper Spec. Still not totally familiar with it, but making progress.
What has blocked me was the fact that I didn't know if it was possible to override certain macro characters.
Well, the pages about SET-MACRO-CHARACTER and SET-DISPATCH-MACRO-CHARACTER don't mention any restriction, so any character is game for macro character.
Nevertheless, if you have instructions that would make me more productive while searching CLHS, I'll receive them with pleasure :-)
Read the non-dictionnary parts to get background information, and use the _permuted_ _index_ to find all the relevant symbols:
http://www.lispworks.com/documentation/HyperSpec/Front/X_Symbol.htm
Here you could have found all the symbols containing CHARACTER and all the symbols containing MACRO, and therefore all the symbols about MACRO-CHARACTERs. Reading all these pages would let you know that you can override any character macro.
Hello,
I've modified the code as follows (also not yet corrected the tilde bug) : What you can see is that insted of restoring the full initial readtable, I only restore the #" macro character previous function (the one I have overriden with (start). This way, I do not reset other -possible- personalized macro character functions. I also provide a (stop) method which restores the previous #" macro character.
I also twiked the main function in order to let the initial string unchanged in case no ${} is used (usefull for a lot of macros that only accept raw strings, such as defpackage, ...).
Please let me know if you think this would not work in a case I haven't seen.
The code follows :
(defpackage "ENHANCED-STRINGS" (:USE "COMMON-LISP") (:EXPORT "START") (:EXPORT "STOP"))
(in-package "ENHANCED-STRINGS")
(defun parse-enhanced-string (string) (loop :with chunks = '() :with args = '() :with start = 0 :for pos = (search "${" string :start2 start) :for end = (and pos (search "}" string :start2 pos)) :while end :do (progn (push (subseq string start pos) chunks) (multiple-value-bind (form next) (read-from-string string t nil :start (+ 2 pos) :end end) (loop :while (and (< next end) (member (aref string next) '(#\space #\newline))) :do (incf next)) (unless (= next end) (error "Junk in ~S" (subseq string pos end))) (push form args)) (setf start (1+ end))) :finally (progn (push (subseq string start) chunks) (if (rest chunks) (return `(format nil ,(format nil "~{~A~^~~A~}" (nreverse chunks)) ,@(nreverse args)))) (return string))))
(defun reader-macro--enhanced-string (stream dblquote) (let ((*readtable* (copy-readtable))) (set-macro-character #" (get-old-macro-character)) (unread-char dblquote stream) (parse-enhanced-string (read stream t nil t))))
(let ((old-macro-character nil)) (defun start () (unless old-macro-character (setf old-macro-character (get-macro-character #")) (set-macro-character #" #'reader-macro--enhanced-string))) (defun stop () (when old-macro-character (set-macro-character #" old-macro-character) (setf old-macro-character nil))) (defun get-old-macro-character () old-macro-character))
Laurent PETIT writes:
Hello,
I've modified the code as follows (also not yet corrected the tilde bug) : What you can see is that insted of restoring the full initial readtable, I only restore the #" macro character previous function (the one I have overriden with (start). This way, I do not reset other -possible- personalized macro character functions. I also provide a (stop) method which restores the previous #" macro character.
I also twiked the main function in order to let the initial string unchanged in case no ${} is used (usefull for a lot of macros that only accept raw strings, such as defpackage, ...).
Please let me know if you think this would not work in a case I haven't seen.
Well, what hasn't been explicitely mentionned, is that any usage of a string where it's _not_ evaluated (which may be a lot, including the macros), wouldn't work anymore.
What if I want to name my package: "PACK${1}" ?
Macros can expand strings given as argument as (quote "string") instead of merely "string".
Compare the two results:
(let ((x 1)) (values (quote "STR ${x}") "STR ${x}"))
That's why cl-interpolate doesn't use the standard macro character, because we want to be able to write both true _literal_ string and interpolated strings.
I'd recommend downloading cl-interpol http://weitz.de/cl-interpol/ it should give you what you are looking for.
CL-USER 2 > (cl-interpol:enable-interpol-syntax)
CL-USER 3 > #?"1 is less than ${most-positive-fixnum}" "1 is less than 8388607"
Cheers, Sean.