July 7, 2008
Porting Perl’s qq to Common Lisp
Perl has the useful qq operator which lets you specify an arbitrary delimiter for the string following it:
# here with exclamation mark: qq!we "often" use "quotes" "here".!
In Common Lisp, this would be useful as well, especially in docstrings and when generating foreign language code (think JavaScript without Parenscript, for example).
Let over Lambda shows us the useful sharp-doublequote reader macro that lets #" and "# act as delimiters.
This already helps a lot and looks very good, but sometimes you have a lot of double quotes and the terminating combination "# inside one string.
Take a look at this piece of JQuery code in Lisp (CL-WHO html generation):
(:a :onclick (format nil "$(\"#content\").load(\"~A.clhp\"); return true;" id))
Sharp-doublequote won’t work here because of "#. We can’t use single quotes here either because Hunchentoot will delimited the onclick part with them. We could probably add a space between " and #, but it would be a kludge and might not work in other cases anyway.
The bottom line is that using a fixed character or character combination won’t work for all cases (except when the delimiter is really long like MIME boundary strings, but this is obviously impractical).
So letting the user choose the delimiter on a case-by-case basis is a smart decision (as long as it is not overused and clutters the code with all sorts of delimiters).
The following code provides this functionality:
(defun |#q-reader| (stream sub-char numarg) (declare (ignore sub-char numarg)) (let ((terminator (read-char stream))) (loop for ch = (read-char stream) until (eql ch terminator) collect ch into chars finally (return (coerce chars 'string))))) (set-dispatch-macro-character #\# #\q #'|#q-reader|)
Quick test:
% clisp -repl qq.lisp [1]> "foo" "foo" [2]> #q|foo| "foo" [3]> #q|foo bar baz| "foo bar baz" [4]> #q!foo bar baz! "foo bar baz" [5]> #q!foo bar "baz! "foo bar \"baz" [6]> #q!Hello! world! ; oops "Hello" [7]> *** - SYSTEM::READ-EVAL-PRINT: variable WORLD! has no value
Comments(6)
CL-INTERPOL (http://www.weitz.de/cl-interpol/) is a library for string interpolation, and has this flexible quoting you lay out here.
That’s nice. I know about CL-INTERPOL, but I thought it just interpolates strings via its read macro.
How much more trickery would be needed to allow also for balanced delimiters?
qq()
qq[]
qq{}
qq
A small change in the parser, the definition of the balanced characters. I also took the liberty of using anaphoric if.
If you don’t want to deal with variable capture, you can always specify the variable’s name:
(defmacro if-let (var test then else) `(let ((,var ,test)) (if ,var ,then ,else)))Nice code, Leslie. It’s good to see Lisp stealing useful features from other languages :)
Hi Leslie!
What about
http://paste.lisp.org/display/77963
This way, you won’t mislead emacs.