read-time string concatenation
By Didier Verna on Tuesday, June 21 2011, 17:53 - Lisp - Permalink
Sometimes, I miss the string concatenation capability of C. I mean, the way that you can split a long string into smaller ones and have them automatically concatenated together. The format
function has a tilde (~) directive that does something along these lines, but there are two problems:
- first, I'm not necessarily facing the problem in
format
calls, - next, my favorite text editor cannot properly indent the string contents when I press the
TAB
key.
Here's an example:
(defclass sample () ((slot :documentation "A very long slot documentation, that doesn't even fit in 80 columns, which is a shame...")))
If that were C code, I could write this:
(defclass sample () ((slot :documentation "A very long slot documentation, " "that doesn't even fit in 80 columns, " "which is a shame...")))
But (thank God) this is not C code. We can come very close to that however. Here's a small reader function that will automatically concatenate strings prefixed with a tilde (~) character (in honor of format
's directive):
(defun tilde-reader (stream char) "Read a series of ~\"string\" to be concatenated together." (declare (ignore char)) (flet ((read-string (&aux (string (read stream t nil t))) (check-type string string "a string") string)) (apply #'concatenate 'string (read-string) (loop :while (char= (peek-char t stream nil nil t) #\~) :do (read-char stream t nil t) :collect (read-string)))))
Let's add it to the current readtable:
(set-macro-character #\~ #'tilde-reader)
And now I can write this:
(defclass sample () ((slot :documentation ~"A very long slot documentation, " ~"that doesn't even fit in 80 columns, " ~"which is a shame...")))
Everyday of my life I thank Common Lisp for being so flexible. The next step would be to make cl-indent.el
aware that tilde-strings really are the same object, and hence should be vertically aligned in all contexts. I'm not there yet. Jeeze, this indentation hacking will never end... :-)
Comments
You could avoid alignment issues by writing a function with a short name and passing the strings as argument. SQL's concatenation operator is ||. You could abuse CL's multiple escape syntax to name a function: (defun || (&rest strings) ...).
Great, I like your hack.
I suffered it too. I usually break the docstrings, but extra newlines are introduced in this way. Therefore, I use a convention to specify lines: single newlines are ignored and double newlines are replaced by single ones.
However it does the indentation problem unresolvable.
I've always used Paul Graham's "mkstr" function for this since I first came across it in On Lisp.
http://www.mail-archive.com/alexand...
http://unintelligible.org/onlisp/on...
@Xach So you mean just use a function to do the concatenation instead of using a macro character? Anyway, using || as a function name is neat ;-)
Thanks for your ongoing attention to all things string/format/documentation related and for taking the time to bring your findings to the interwebs.
One thing i find lacking whether for your ~ reader (and prob. Xach's `||' function as well) is the ability to specify a "fill column".
It would be nice to have a lambda list like this:
(stream char &optional (default-fill *read-tilde-refill*))
where *read-tilde-refill* defaults to 80 and has the type signature:
(or null (integer 1 *))
and where DEFAULT-FILL parameter could take one of the forms:
NIL -- no fill
T -- fill to *read-tilde-refill* columns)
<INT> -- fill to <INT> columns. <INT> is an integer of type (integer 1 *)
Normally, one might add this layer functionality in a wrapper function. However, in so much as #\~ is a macro-character it might make sense to directly accommodate for a default w/r/t filling.
This would come in especially handy around documentation related contexts, i.e. for `CL:DOCUMENTATION' one could dynamically bind *read-tilde-refill* to suit particular filling needs.
@mon-key
Your request seem to be about formatting the DISPLAY of documentation material, whereas here I was concerned about formatting the CODE itself. So I fail to see the relation between the two...
Hi,
I like the idea, however - it does not work smoothly e.g. in Slime and sbcl. I get problems because (shortly) it uses reading with *read-supress* to get to place in a file, and thus your check-type fails.
So one should probably handle the *read-suppress* situation specifically.
(Other fix I did for myself was to insert space or newline between strings, using ~" vs ~@", but this is more of a taste question)
@Tomas
Can you provide a specific scenario which exhibits the problem?
I understand what you say but I've never encountered the problem in any SBCL+Slime session.
Thank you.
I do slime-compile-and-load-file (C-c C-k). It finds some compiler notes and tries to scroll to them, and breaks on it.
Error is
The value of STRING is NIL, which is not a string
Relevant stack trace:
(SWANK-BACKEND::SKIP-TOPLEVEL-FORMS 6 #<SB-SYS:FD-STREAM for XXX>)
(SWANK-BACKEND::SOURCE-PATH-FILE-POSITION (5) XXX)
(SWANK-BACKEND::LOCATE-COMPILER-NOTE ..)
and skip-toplevel-forms is defined (for sbcl at least) as
(defun skip-toplevel-forms (n stream)
(let ((*read-suppress* t))
(dotimes (i n)
(read stream))))
Sorry for poor formatting, I hope it makes sense. I suppose you would hit the problem when you comment something out with #+nil as well.
An impressive share! I've just forwarded this onto
a co-worker wwho has been conducting a little homework on this.
And he in fact ordered me breakfast due tto the fact that I stumbled upon it ffor him...
lol. So let me reword this.... Thanks for the meal!!
But yeah, thanx for spending the time to discuss this topic
here on your internet site.
مركز مبصرون لعلاج السحرالاسود و سحر الطاعة
اتصل الان 01211644114 لحجز موعد
مصر , القاهرة , صلاح سالم عمارات العبور
يمتد نشاطمركز مبصرون في علاج السحر الي جميع انحاء
العالم
Calⅼ Now 01211644114
It's going to be finish of mine day, however
before end I am reading this great article to increase my experience.