By: Peter McGoron
Raw string syntax are lexical syntax for strings that do not interpret
escapes inside of them. They are useful in cases where the string data
has a lot of characters like \\ or " that would otherwise have
to be escaped. The raw string syntax in this document is derived from
C++11's raw string literals.
Are there any other characters that need to be excluded from delimiters?
Daphne Preston-Kendal's proposal:
raw strings that start with #" and end with "#, no escapes. The string
#" can appear in inside of the string if paired with "# (that is, the strings
nest like block comments). The only escape sequences are \#" and \"#, which
insert the sequences after the backslash without starting a new nesting level.
Any other sequence that starts with \ is not an escape sequence and inserts the
backslash.
Many programming languages have raw string syntax: to name a few, Rust, C++, Python, Go, C#, and Zig. For a more complete list of languages referenced while making this proposal, see this wiki page.
Scheme implementations generally do not have raw string syntax. Two exceptions
are
CHICKEN
and
Gambit,
which use heredoc
style syntax. Both are Scheme→C compilers that require inserting
C code into their Scheme programs.
Some Scheme implementations have the ability to extend their readers,
so some have raw strings as external extensions. A reader extension
for a similar syntax for raw strings is available for Guile.
Daphne Preston-Kendal proposed a syntax for raw strings using #" in
another document.
The matter of raw string syntax in the R7RS-large was discussed in a WG2 meeting on November 21st, 2025 with no consensus. It has also been discussed on the issue tracker for the R7RS-Large process.
This SRFI proposes the use of raw strings based off of C++'s syntax. C++'s syntax is a raw string syntax with a customizable delimiter that allows for the use of a wide variety of characters. Examples of raw strings and their equivalent regular Scheme strings:
#""a"" ; => "a"
#""\begin{document}"" ; => "\\begin{document}"
#"--")")")"-""--" ; => ")\")\")-\")"
The use of a customizable delimiter means that extra whitespaces
are not required before and after the string when the raw string ends
with the text "". When customizable delimiters with a wide variety
of characters cannot be used (like
in Rust or Markdown), some strings require spaces after the left delimiter and
before the right delimiter: For example, `` `markdown code spans containing code spans` ``.
A customizable delimiter can also be used for documentation of the
inside of the raw string itself. For instance, SQL syntax can use
a delimiter that starts with sql, LaTeX syntax can use a delimiter
that starts with latex. A text editor could then switch its syntax
highlighting within the raw string.
C++ style strings have the benefit that they are convenient for inline raw strings and multiline raw strings, unlike heredoc-style syntax, which is cumbersome for single line raw strings.
This SRFI does not handle leading, trailing, or indentation whitespace in any special way: they are all preserved in the resultant string. This is the least surprising option, and further string processing can be done by the programmer. This is the behavior of Rust and C++. Other languages like C# have special whitespace handling.
Some "raw" string syntaxes allow for interpolation, or have different
types of escape sequences. String interpolation in Scheme is the
subject of SRFI-109. Interpolation makes string processing much more
complicated and is not extensible, while also not making the strings
truly "raw." Interpolation of strings in Scheme is better accomplished by
syntax-case macros.
Equivalent syntax for string notated bytevectors in SRFI-207 or for vertical-bar identifiers is not included.
The grammar of raw strings is not context free. The following grammar describes the creation of a raw string literal for any valid delimiter X:
⟨raw string (X)⟩ ⩴ #" X " ⟨raw string internal (X)⟩ " X "
⟨raw string internal (X)⟩ ⩴ Any sequence of zero or more characters that does not contain " X " as a subsequence
⟨valid delimiter⟩ ⩴ Any sequence of zero or more characters not including "
Note that although valid delimiters look like strings when used, they do not interpret escape sequences inside of them.
A raw string, when read, is a string. They are allowed wherever a regular Scheme string is allowed. The grammar of Scheme is modified so that the ⟨string⟩ production becomes
⟨string⟩ ⩴ " ⟨string element⟩* "
| ⟨raw string (X)⟩ for X satisfying ⟨valid delimiter⟩
In particular, raw strings are allowed in the include and
include-library-declaration forms described in the R7RS.
The following examples all evaluate to #t.
#"""" ; → ""
#""a"" ; → "a"
#""\"" ; → "\\"
#"-"""-" ; → "\""
#"-" " "-" ; → " \" "
#"-"#""a"""-" ; → "#\"\"a\"\""
#"-"ends with ""-" ; → "ends with \\\"
#""multiline
string"" ; → "multiline\nstring"
#""
no whitespace stripping"" ; → "\n no whitespace stripping
#""\(?(\d{3})\D{0,3}(\d{3})\D{0,3}(\d{4})""
; → "\\(?(\\d{3})\\D{0,3}(\\d{3})\\D{0,3}(\\d{4})"
; Example from SRFI-264
The following example shows how a raw string can be used to embed other syntaxes as a string. The syntax inside of the string is SRFI-119 wisp syntax.
#"wisp-EOF"
define: hello-name name
string-append "Hello," name "!"
"wisp-EOF"
; ⇒ "\ndefine: hello-name name\n string-append \"Hello,\" name \"!\"\n"
One use of raw strings is in “docstring” documentation, where " and \ may be
used to document strings. The following example is from Daphne-Preston Kendal:
(define (parse-url url-string)
#""Given a URL as a string, returns a Parsed-URL record with the
components of that URL.
(parse-url "https://example.org/~smith/?record")
=> #<Parsed-URL protocol: "https" domain: "example.org"
path: "/~smith/" query: "?record">""
...)
A portable implementation is impossible in general. However, some implementations allow modifying the reader. The following code works with CHICKEN-5 and Guile 3.
The last part doubles as a simple test suite.
(define (read-raw-string port)
;; This parser starts reading after `"`.
;; In the given examples, the parser starts at the dot:
;;
;; #"."asdf""
;; #".--"#"()""--"
(define (read-char* location)
(let ((ch (read-char port)))
(if (eof-object? ch)
(error (list "eof in raw string literal" location))
ch)))
(define delimiter
(do ((ch (read-char* 'delim) (read-char* 'delim))
(acc '(#\") (cons ch acc)))
((char=? ch #\")
(reverse (cons #\" acc)))))
(call-with-port (open-output-string)
(lambda (out)
(define (read-delimiter n rest-of-delimiter)
(if (null? rest-of-delimiter)
(get-output-string out)
(let ((ch (read-char* 'check)))
(if (char=? ch (car rest-of-delimiter))
(read-delimiter (+ n 1) (cdr rest-of-delimiter))
(do ((n n (- n 1))
(delimiter delimiter (cdr delimiter)))
((zero? n) (read-raw ch))
(write-char (car delimiter) out))))))
(define (read-raw ch)
(if (char=? ch (car delimiter))
(read-delimiter 1 (cdr delimiter))
(begin (write-char ch out)
(read-raw (read-char* 'read)))))
(read-raw (read-char* 'read)))))
(cond-expand
(chicken (import (chicken read-syntax))
(set-sharp-read-syntax! #\" read-raw-string))
(guile (read-hash-extend #\"
(lambda (_ port)
(read-raw-string port))))
(else (error "your implementation is not supported")))
(define (test x y)
(display (string=? x y)) (newline))
(test "" #"""")
(test "a" #""a"")
(test "\\" #""\"")
(test "\"" #"-"""-")
(test "\\\"" #"-"\""-")
(test "#\"()\"" #"-"#"()""-")
(test "#\"\"a\"\"" #"-"#""a"""-")
(test "ends with \\\"" #"-"ends with ""-")
(test "multiline\nstring" #""multiline
string"")
(test "\n no whitespace stripping" #""
no whitespace stripping"")
The original C++ proposal was written by Bemen Dawes. The last C++ proposal was written by Bemen Dawes and Lawrence Crowl. The raw string syntax was ratified in C++11.
The choice of #" was copied from Daphne Preston-Kendal. John Cowan
suggested double quotes in place of balanced parentheses for raw strings.
Thanks to the members of WG2 who discussed the issue.
© 2025 Peter McGoron
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.