├── .github └── workflows │ ├── guile-2.2.yml │ └── guile-3.0.yml ├── LICENSE ├── README.md ├── mod └── raw-strings.scm ├── srfi.md └── test.scm /.github/workflows/guile-2.2.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Guile 2.2 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: update 16 | run: | 17 | sudo apt update 18 | sudo apt install guile-2.2 19 | - uses: actions/checkout@v3 20 | - name: test 21 | run: | 22 | guile -L mod test.scm 23 | -------------------------------------------------------------------------------- /.github/workflows/guile-3.0.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Guile 3.0 3 | 4 | on: 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: update 16 | run: | 17 | sudo apt update 18 | sudo apt install guile-3.0 19 | - uses: actions/checkout@v3 20 | - name: test 21 | run: | 22 | guile -L mod test.scm 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # guile-raw-strings [![guile-3.0](https://github.com/lloda/guile-raw-strings/actions/workflows/guile-3.0.yml/badge.svg)](https://github.com/lloda/guile-raw-strings/actions/workflows/guile-3.0.yml) [![guile-2.2](https://github.com/lloda/guile-raw-strings/actions/workflows/guile-2.2.yml/badge.svg)](https://github.com/lloda/guile-raw-strings/actions/workflows/guile-2.2.yml) 3 | 4 | `guile-raw-strings` is a reader extension for [GNU Guile](https://www.gnu.org/software/guile) that lets you write verbatim strings such as 5 | 6 | #R-(quotes " and escapes \ and newlines 7 | can " freely be used " here)- 8 | 9 | where you'd normally need escapes: 10 | 11 | "quotes \" and escapes \\ and newlines\n can \" freely be used \" here" 12 | 13 | This comes in handy for docstrings, regexps, etc. 14 | 15 | The string between the `#R` and the `(` is the 'delimiter'. The string ends with a `)` followed by the delimiter. In the example above, the delimiter is `-`. The delimiter can be empty, as in `#R(put your '\"\\)` for `"put your '\\\"\\\\"`. 16 | 17 | You can also use `[]` or `""` instead of `()` to ‘delimit the delimiter’[1](#f1). This means that you cannot use the characters `[("` as part of the delimiter. Whitespace in the delimiter is forbidden. 18 | 19 | * `#R(hello)` ⇒ `hello` 20 | * `#R"hello"` ⇒ `hello` 21 | * `#R[hello]` ⇒ `hello` 22 | * `#Rdo-not-repeat(hello)do-not-repeat` ⇒ `hello` 23 | 24 | The open-close pair must be matched, but the delimiter must be repeated verbatim. 25 | 26 | * `#R("hello")` ⇒ `"hello"` —empty delimiter, open-close-pair is `()`. 27 | * `#R"(hello)"` ⇒ `(hello)` —since `""` is an open-close pair, this also has an empty delimiter. 28 | * `#R]"hello"]` ⇒ `hello` —here the delimiter is `]` and the open-close pair is `""`. 29 | * `#R["hello"]` ⇒ `"hello"` —here the delimiter is empty and the open-close pair is `[]`. 30 | 31 | The extension should run on Guile 2.2 or later. To enable it, install `mod/raw-strings.scm` in your module path and then ``(import (raw-strings))``. 32 | 33 | Run the test with 34 | 35 | $GUILE -L mod -s test.scm 36 | 37 | I hope you find this useful. 38 | 39 | ## References 40 | 41 | 1. Revised⁵ Report on the Algorithmic Language Scheme, Feb. 1998. §6.3.5: Strings. 42 | 2. Per Bothner, SRFI-109: Extended string quasi-literals, 2013. 43 | 3. Scheme registry: # lexical syntax. 44 | 4. *Raw string literal* in 45 | 5. *raw strings* in 46 | 6. 47 | 7. s7: A Scheme implementation. 48 | 8. Chicken Scheme: Non-standard read syntax. 49 | 50 | — 51 | 52 | ¹ You can configure the open-close pairs, as well as the extension character `R`, with the variables `openc`, `closec` and `extension-char` at the top of the source. A single open-close pair seems preferable, if everyone agrees on what that should be. [↩](#a1) 53 | -------------------------------------------------------------------------------- /mod/raw-strings.scm: -------------------------------------------------------------------------------- 1 | ; -*- mode: scheme; coding: utf-8 -*- 2 | ;; Reader extension for raw strings 3 | ;; by lloda@sarc.name 2017, 2019, 2022 4 | ;; This code is in the public domain. 5 | 6 | ;; Based on R"delimiter(raw_characters)delimiter" 7 | ;; in http://en.cppreference.com/w/cpp/language/string_literal. 8 | ;; Having #R, the quotes are unnecessary, so I consider them part of the delimiter; 9 | ;; you can ellide them. 10 | 11 | (define-module (raw-strings) 12 | #:use-module ((ice-9 rdelim))) 13 | 14 | ; configuration. 15 | (eval-when (expand load eval) 16 | (define openc "([\"") 17 | (define closec ")]\"") 18 | (define extension-char #\R)) 19 | 20 | (define (reader-extension-raw-string chr port) 21 | (define (char-please port) 22 | (let ((c (read-char port))) 23 | (if (eof-object? c) 24 | (throw 'end-of-file-reading-raw-string) 25 | c))) 26 | (let* ((fix-open (read-delimited openc port 'split)) 27 | (fix (car fix-open)) 28 | (open (cdr fix-open)) 29 | (close 30 | (let-syntax ((pick-close 31 | (lambda (stx) 32 | (syntax-case stx () 33 | ((_ o) 34 | #`(case o 35 | #,@(map (lambda (a b) `((,a) ,b)) 36 | (string->list openc) (string->list closec)) 37 | (else (throw 'raw-string-delimiter-not-found fix)))))))) 38 | (pick-close open)))) 39 | (when (string-index fix char-whitespace?) 40 | (throw 'raw-string-delimiter-has-whitespace fix)) 41 | (let search-delim ((c (char-please port)) (s '())) 42 | (if (eqv? close c) 43 | (let search-close ((ss (list close)) (i 0)) 44 | (if (= i (string-length fix)) 45 | (list->string (reverse! s)) 46 | (let ((c (char-please port))) 47 | (if (eqv? (string-ref fix i) c) 48 | (search-close (cons c ss) (+ 1 i)) 49 | (search-delim c (append ss s)))))) 50 | (search-delim (char-please port) (cons c s)))))) 51 | 52 | (read-hash-extend extension-char reader-extension-raw-string) 53 | -------------------------------------------------------------------------------- /srfi.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 16 | 17 | 18 | # SRFI-??? - Raw string literals 19 | 20 | ## Abstract 21 | 22 | Raw string literals use configurable delimiters as an alternative to the escaping syntax using in the standard string literals. The goal of this SRFI is to offer a common syntax for these literals. 23 | 24 | 25 | ## Issues 26 | 27 | * See [9] for some discussion. Needs stronger rationale to go there again. 28 | 29 | * Should more than one OPEN/CLOSE pair be offered, or be configurable? 30 | 31 | The sample implementation allows `"`/`"`,`(`/`)`, and `[`/`]`. This has the advantage that one may pick `(`/`)` when the string has `"` in it and so on, the way `'` and `"` are mixed in some languages, without having to add a delimiter. Also using matched characters and not the same might be somewhat clearer. SRFI-109 uses `{`/`}`. 32 | 33 | * Should the literal prefix character be something different from `#R`? 34 | 35 | C++ raw string literals use `R`. Python uses `r`. Presumably this is for ‘raw’. Lower case seems more in line with Scheme practice. It is noted that `#r` is used in s7 Scheme [7] with a different meaning. 36 | 37 | 38 | ## Rationale 39 | 40 | The standard string literal syntax [1] uses the character `"` as delimiter, which needs to be escaped when it's part of the string. The escape character `\` must itself be escaped. Escaping can easily get out of hand when there is more than one level of escaping or quoting involved, e.g. when writing regular expressions. 41 | 42 | With raw string literals, the delimiter can be different for each string literal. One is free to choose a delimiter that doesn't appear in the string, which removes the need for escapes entirely. 43 | 44 | Raw string literals are present in other languages [4, 5] and in some Schemes as a non-standard syntax extension [6, 8]. SRFI-109 [2] provides an extension to the string literal syntax that can remove the need for escapes in most cases. The syntax `&{` STRING `}` proposed there is similar to the syntax `#R"` STRING `"` proposed here. SRFI-109 still needs to handle escapes, and adds its own for newlines, substitutions, named characters, and so on. A syntax with user-defined delimiters is described, `&!` DELIMITER `{` STRING `}` DELIMITER, which is again similar to the syntax `#R` DELIMITER `"` STRING `"` DELIMITER described here. It is unclear if all the special escapes are also supported in this case. Chicken Scheme [6] offers the syntax `#<<` DELIMITER NEWLINE STRING NEWLINE DELIMITER NEWLINE for ‘multiline string constants’, and a variant using `#<#` that allows for escapes and substitutions, as SRFI-190 does. 45 | 46 | The prefix `#R` is used to avoid incompatibilities with existing syntax and to simplify implementation. This proposal could be made substantially compatible with SRFI-109 by using `{`/`}` for OPEN/CLOSE, using `&` as prefix instead of `#R`, and requiring any non-empty user defined DELIMITER to start with `!`. But the convention of using `&` for extended literals, used in SRFI-107, 108 and 109, has not been adopted generally, and those SRFIs are supported only in one implementation, while most Schemes support extended literals starting with `#`. 47 | 48 | 49 | ## Specification 50 | 51 | A raw string literal is a sequence of characters made of the following blocks, with nothing between them. 52 | 53 | > `#R` DELIMITER OPEN STRING CLOSE DELIMITER 54 | 55 | * `#R` are the two literal characters `#R`. 56 | * OPEN is the character `"`. 57 | * CLOSE is the character `"`. 58 | * DELIMITER is a sequence of characters excluding control characters, whitespace, OPEN, or CLOSE. DELIMITER may be an empty sequence. 59 | * STRING is a sequence of characters where the sequence CLOSE DELIMITER does not apear. 60 | 61 | OPEN, CLOSE, and DELIMITER may appear freely in STRING as long as the sequence CLOSE DELIMITER does not. 62 | 63 | The raw string literal evaluates to a string containing the sequence of characters in STRING verbatim. No escapes are supported. 64 | 65 | ### Examples 66 | 67 | Using an empty DELIMITER: 68 | 69 | * `#R"hello"` ⇒ `"hello"` 70 | * `#R"hel\lo"` ⇒ `"hel\\lo"` 71 | 72 | Using `-` as DELIMITER: 73 | 74 | * `#R-"hel\"lo"-` ⇒ `"hel\\\"lo"` 75 | * `#R-"he-l\"lo"-` ⇒ `"he-l\\\"lo"` 76 | * `#R-"he-"l\"lo"-` ⇒ `"he-\"l\\\"lo"` 77 | * `#R-"he-"-` ⇒ `"he-"` 78 | * `#R-"he-""-` ⇒ `"he-\""` 79 | * `#R-"he-"""-` ⇒ `"he-\"\""` 80 | 81 | Using `***` as DELIMITER: 82 | 83 | * `#R***"hel\"lo"***` ⇒ `"hel\\\"lo"` 84 | * `#R***"he***l\"lo"***` ⇒ `"he***l\\\"lo"` 85 | * `#R***"he***"l\"lo"***` ⇒ `"he***\"l\\\"lo"` 86 | * `#R***"he***"**"***` ⇒ `"he***\"**"` 87 | * `#R***"he***"**""***` ⇒ `"he***\"**\""` 88 | * `#R***"he***"***` ⇒ `"he***"` 89 | * `#R***"he***""***` ⇒ `"he***\""` 90 | * `#R***"he***"""***` ⇒ `"he***\"\""` 91 | 92 | A longer example with newlines, using `|` as delimiter. 93 | 94 | #R|"quotes " and escapes \ and newlines 95 | can " freely be used " here"| 96 | 97 | ⇒ 98 | 99 | "quotes \" and escapes \\ and newlines\n can \" freely be used \" here" 100 | 101 | ## Implementation 102 | 103 | The sample implementation for Guile uses a reader extension. 104 | 105 | ## References 106 | 107 | 1. Revised⁵ Report on the Algorithmic Language Scheme, Feb. 1998. §6.3.5: Strings. 108 | 2. Per Bothner, SRFI-109: Extended string quasi-literals, 2013. 109 | 3. Scheme registry: # lexical syntax. 110 | 4. *Raw string literal* in 111 | 5. *raw strings* in 112 | 6. 113 | 7. s7: A Scheme implementation. 114 | 8. Chicken Scheme: Non-standard read syntax. 115 | 9. SchemeCrossReference: Final SRFIs and their support. 116 | 9. Discussion at `srfi-discuss@srfi.schemers.org`: 117 | 118 | ## Copyright 119 | 120 | © 2022 Daniel Llorens 121 | 122 | 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: 123 | 124 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 125 | 126 | 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. 127 | -------------------------------------------------------------------------------- /test.scm: -------------------------------------------------------------------------------- 1 | ; -*- mode: scheme; coding: utf-8 -*- 2 | ; Test reader extension for raw strings 3 | 4 | (import (raw-strings) (srfi srfi-26) (srfi srfi-64)) 5 | 6 | (define (test-reader s) 7 | (call-with-input-string s 8 | (cute (@@ (raw-strings) reader-extension-raw-string) #\R <>))) 9 | 10 | (test-begin "raw-strings") 11 | (test-error 'raw-string-delimiter-not-found (test-reader "xxx||")) 12 | (test-error 'end-of-file-reading-raw-string (test-reader "xxx(thest|\"ring)xx")) 13 | (test-equal "thest|\"ring" (test-reader "xxx(thest|\"ring)xxx")) 14 | (test-equal "thestring)xxyTHENMORE" (test-reader "xxx(thestring)xxyTHENMORE)xxx")) 15 | (test-equal "zeroprefix" (test-reader "(zeroprefix)")) 16 | (test-equal "thest|\\\"ring" #Rxxx(thest|\"ring)xxx) 17 | (test-equal "thestring)xxyTHENMORE" #Rxxx(thestring)xxyTHENMORE)xxx) 18 | (test-equal "zero(\\prefix" #R(zero(\prefix)) 19 | (test-equal "hello" #R"hello") 20 | (test-equal "(hello)" #R"(hello)") 21 | (test-equal "\"hello\"" #R("hello")) 22 | (test-equal "(hello)" #R[(hello)]) 23 | (test-equal #R-"he-""- "he-\"") 24 | (test-equal #R-(he-))- "he-)") 25 | (test-equal #R-(he-)))- "he-))") 26 | (test-equal #R-(he-()- "he-(") 27 | (test-equal #R***"he***"**"*** "he***\"**") 28 | (test-equal #R***"he***""*** "he***\"") 29 | 30 | #R-(This is a long string, 31 | full of bad stuff like \ ' " \" \n ) ( etc. 32 | that finally ends.)- 33 | 34 | #R-"This is a long string, 35 | full of bad stuff like \ ' " \" \n ) ( etc. 36 | that finally ends."- 37 | 38 | (define error-count (test-runner-fail-count (test-runner-current))) 39 | (test-end "raw-strings") 40 | (exit error-count) 41 | --------------------------------------------------------------------------------