├── COPYING ├── README.org ├── pythonic-string-reader.asd └── pythonic-string-reader.lisp /COPYING: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without modification, 2 | are permitted provided that the following conditions are met: 3 | 4 | 1. Redistributions of source code must retain the above copyright notice, this 5 | list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, 8 | this list of conditions and the following disclaimer in the documentation and/or 9 | other materials provided with the distribution. 10 | 11 | 3. Neither the name of the copyright holder nor the names of its contributors 12 | may be used to endorse or promote products derived from this software without 13 | specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 19 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 22 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | 2 | * Pythonic String Reader 3 | 4 | This is a piece of code stolen from Yury Sulsky which has been slightly 5 | modified/improved by myself. It sets up some reader macros that make it simpler 6 | to input string literals which contain backslashes and double quotes. This is 7 | very useful for writing complicated docstrings and, as it turns out, writing 8 | code that contains string literals that contain code themselves. 9 | 10 | ** Enabling the syntax 11 | 12 | Pythonic String Reader is implemented using modern read-table management 13 | libraries like Named Readtables, so it should be even more unintrusive than it 14 | was originally designed. 15 | 16 | To use the this read macro, you can simply use the readtable 17 | =pythonic-string-syntax= in the package by putting the following at the 18 | beginning of your files: 19 | 20 | #+begin_src lisp 21 | (in-readtable pythonic-string-syntax) 22 | #+end_src 23 | 24 | Or use any of the ways Named Readtables allows you to define and combine 25 | readtables, for instance: 26 | 27 | #+begin_src lisp 28 | (defreadtable my-readtable 29 | (:merge :standard) 30 | (:merge pythonic-string-syntax)) 31 | #+end_src 32 | 33 | ... or ... 34 | 35 | #+begin_src lisp 36 | (defreadtable my-readtable 37 | (:merge :standard) 38 | (:macro-char #\" #'pythonic-string-reader::read-multiline-string t)) 39 | 40 | (in-readtable my-readtable) 41 | #+end_src 42 | 43 | *** I don't want to use Named Readtables 44 | 45 | To enable the reader syntax, run =enable-pythonic-string-syntax= and to disable 46 | use =disable-pythonic-string-syntax=. 47 | 48 | ** Usage 49 | 50 | The string reader will treat any bit of code that contains three consecutive 51 | double quotes specially. If it finds three consecutive double quotes, it will 52 | start reading and won't stop until it finds another three consecutive double 53 | quotes. All text in between is taken as literal. It is impossible to escape 54 | anything. 55 | 56 | As an extension, if the reader finds four consecutive double quotes (i.e. it 57 | finds the opening three double quotes, then finds one more immediately after 58 | those), it will do the same as above, except it will not end until it reaches 59 | another four consecutive double quotes. This is particularly useful if you want 60 | to trick your text editor into treating the string you are writing as normal 61 | code instead of a string. 62 | 63 | *** Examples 64 | 65 | Note that in these examples I still balance quotes. This is needed so that 66 | Slime doesn't get confused and think there is still input to be had. This is 67 | not the case when code is loaded or compiled using the =load= or =compile-file= 68 | commands (which is what users will experience). 69 | 70 | #+begin_src 71 | CL-USER> """ hello\ """ 72 | " hello\\ " 73 | CL-USER> """ hello\ """" " 74 | " " 75 | CL-USER> """hello""" 76 | "hello" 77 | CL-USER> """ \ """ 78 | " \\ " 79 | CL-USER> """ " " """ 80 | " \" \" " 81 | CL-USER> """" hello """" 82 | " hello " 83 | CL-USER> """" hello """ " """" 84 | " hello \"\"\" \" " 85 | #+end_src 86 | 87 | ** TODO 88 | 89 | Perhaps I will integrate with CL-Syntactic-Sugar in the future, as that used to 90 | be a way to wrangle all of these reader macros in a sane way. I feel like 91 | Named-Readtables has accomplished this in a cleaner way though. 92 | 93 | -------------------------------------------------------------------------------- /pythonic-string-reader.asd: -------------------------------------------------------------------------------- 1 | 2 | (asdf:defsystem #:pythonic-string-reader 3 | :author "Yury Sulsky and Zach Kost-Smith" 4 | :maintainer "Zach Kost-Smith " 5 | :description "A simple and unintrusive read table modification that allows for 6 | simple string literal definition that doesn't require escaping characters." 7 | :components ((:file "pythonic-string-reader")) 8 | :serial t 9 | :depends-on (:named-readtables)) 10 | -------------------------------------------------------------------------------- /pythonic-string-reader.lisp: -------------------------------------------------------------------------------- 1 | 2 | (defpackage :pythonic-string-reader 3 | (:use :cl :named-readtables) 4 | (:export 5 | #:enable-pythonic-string-syntax 6 | #:pythonic-string-syntax 7 | #:disable-pythonic-string-syntax)) 8 | 9 | ;; Modified by code by Yury Sulsky 10 | 11 | ;; The original code stated that this was for reading multi-line strings. This 12 | ;; isn't at all an issue in Common Lisp. What this really does is allow us to 13 | ;; write strings without the need to escape special characters. 14 | 15 | ;; I am providing two modes of operation. The first is to use three consecutive 16 | ;; quotation marks to mark the beginning and ending of your string. Since there 17 | ;; is an odd number of quotes on each side of the string, this will naturally be 18 | ;; interpretted by your editor as a string. The second mode is to use four 19 | ;; consecutive quotes to mark your string. In this case, the editor will see an 20 | ;; even number of quotes before and after, hinting that it should treat it as 21 | ;; Lisp code. This is particularly useful if you are writing Lisp code in a 22 | ;; string but would like to use the editors development tools while you do it. 23 | ;; This might seem like a rare case, but I find it comes up often enough and it 24 | ;; doesn't do any harm. 25 | 26 | ;; Also, as there are two syntactic markers for forming pythonic style strings, 27 | ;; escape free strings, we now have the ability to include a pythonic string 28 | ;; within our pythonic string. This is a useful feature if you ever want to 29 | ;; read the string you are defining. 30 | 31 | (in-package :pythonic-string-reader) 32 | 33 | (let ((normal-string-reader 34 | ;; Grab the original reader from the original readtable (is this the 35 | ;; right thing to do? Seems like the most sane thing.) 36 | (get-macro-character #\" (copy-readtable nil)))) 37 | (defun read-multiline-string (stream c) 38 | (let ((buffer ())) 39 | (when (not (char= #\" (peek-char nil stream))) 40 | (return-from read-multiline-string 41 | (funcall normal-string-reader stream c))) 42 | (read-char stream) 43 | 44 | (when (not (char= #\" (peek-char nil stream))) 45 | (return-from read-multiline-string "")) 46 | (read-char stream) 47 | 48 | ;; Eat one last quote if given. This means that we can use three or 49 | ;; four consequtive double quotes to designate a multi-line string 50 | (let ((four-quote-string 51 | (if (char= #\" (peek-char nil stream)) 52 | (progn (read-char stream) t) 53 | nil))) 54 | (do ((chars (if four-quote-string 55 | (list (read-char stream) 56 | (read-char stream) 57 | (read-char stream) 58 | (read-char stream)) 59 | (list (read-char stream) 60 | (read-char stream) 61 | (read-char stream))) 62 | (cdr (nconc chars (list (read-char stream)))))) 63 | ((every #'(lambda (c) (eq c #\")) chars) 64 | (coerce (nreverse buffer) 'string)) 65 | (push (car chars) buffer)))))) 66 | 67 | ;; add python-style multi-line strings except also except four quote 68 | ;; delimitation so indentation works properly 69 | (defreadtable pythonic-string-syntax 70 | (:merge :standard) 71 | (:macro-char #\" #'read-multiline-string t)) 72 | 73 | (defun enable-pythonic-string-syntax () 74 | (set-macro-character #\" #'read-multiline-string)) 75 | 76 | (defun disable-pythonic-string-syntax () 77 | (set-macro-character #\" (get-macro-character #\" (copy-readtable nil)))) 78 | --------------------------------------------------------------------------------