~ chicken-core (chicken-5) 1ab62dd3946d48014961e846c3546276c73d4d2a
commit 1ab62dd3946d48014961e846c3546276c73d4d2a Author: Peter Bex <peter@more-magic.net> AuthorDate: Tue Nov 1 15:18:26 2016 +0100 Commit: Evan Hanson <evhan@foldling.org> CommitDate: Sun Nov 6 11:09:38 2016 +1300 Improve read/write invariance of keywords (#1332). Keywords are now treated more like symbols are: when they are written, we check for readability, which means they'll be pipe-delimited if they contain "special" characters. The reader now also uses the same "extended token" reader for keywords using the "portable" representation as the style-specific reader, so that it's possible to enter keywords containing "special" characters when using this style. We now also support empty keywords, which can be entered through the "portable" syntax using quotation, i.e., as #:||. Currently, ||: and :|| are not recognised as keywords, but as a symbol of one character, the colon. Ie, it's treated the same as : by itself. Signed-off-by: Evan Hanson <evhan@foldling.org> diff --git a/NEWS b/NEWS index 3d148a19..d3c9b403 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,9 @@ - Runtime system: - "time" macro now shows peak memory usage (#1318, thanks to Kooda). +- Core libraries: + - Keywords are more consistently read/written, like symbols (#1332). + 4.11.1 - Security fixes diff --git a/library.scm b/library.scm index ef5bdb06..00167558 100644 --- a/library.scm +++ b/library.scm @@ -2574,8 +2574,8 @@ EOF (##sys#read-char-0 port) ) ((eq? c #\.) (##sys#read-char-0 port) - (let ([c2 (##sys#peek-char-0 port)]) - (cond [(or (char-whitespace? c2) + (let ((c2 (##sys#peek-char-0 port))) + (cond ((or (char-whitespace? c2) (eq? c2 #\() (eq? c2 #\)) (eq? c2 #\") @@ -2589,22 +2589,26 @@ EOF (##sys#read-error port (starting-line "missing list terminator") - end) ) ] - [else + end))) + (else (r-xtoken (lambda (tok kw) (let* ((tok (##sys#string-append "." tok)) (val - (if kw - (build-keyword tok) - (or (and (char-numeric? c2) - (##sys#string->number tok)) - (build-symbol tok)))) - (node (cons val '())) ) + (cond ((and (string=? tok ".:") + (eq? ksp #:suffix)) + ;; Edge case: r-xtoken sees + ;; a bare ":" and sets kw to #f + (build-keyword ".")) + (kw (build-keyword tok)) + ((and (char-numeric? c2) + (##sys#string->number tok))) + (else (build-symbol tok)))) + (node (cons val '()))) (if first (##sys#setslot last 1 node) (set! first node) ) - (loop node) ))) ] ) ) ) + (loop node) ))) ) ) ) ) (else (let ([node (cons (readrec) '())]) (if first @@ -2693,10 +2697,6 @@ EOF (##sys#read-char-0 port) (loop (##sys#peek-char-0 port) (cons c lst)) ) ) ) ) - (define (r-next-token) - (r-spaces) - (r-token) ) - (define (r-symbol) (r-xtoken (lambda (str kw) @@ -2710,9 +2710,13 @@ EOF (cond ((or (eof-object? c) (char-whitespace? c) (memq c terminating-characters)) - (if (and skw (eq? ksp #:suffix)) + ;; The not null? checks here ensure we read a + ;; plain ":" as a symbol, not as a keyword. + (if (and skw (eq? ksp #:suffix) + (not (null? (cdr lst)))) (k (##sys#reverse-list->string (cdr lst)) #t) - (k (##sys#reverse-list->string lst) pkw))) + (k (##sys#reverse-list->string lst) + (and pkw (not (null? lst)))))) ((memq c reserved-characters) (reserved-character c)) (else @@ -2820,9 +2824,7 @@ EOF (define (build-keyword tok) (##sys#intern-symbol - (if (eq? 0 (##sys#size tok)) - ":" - (##sys#string-append kwprefix tok)) )) + (##sys#string-append kwprefix tok))) ;; now have the state to make a decision. (set! reserved-characters @@ -2930,10 +2932,14 @@ EOF (else (list 'location (readrec)) )))) ((#\:) (##sys#read-char-0 port) - (let ((tok (r-token))) - (if (eq? 0 (##sys#size tok)) - (##sys#read-error port "empty keyword") - (build-keyword tok)))) + (let ((c (##sys#peek-char-0 port))) + (fluid-let ((ksp #f)) + (r-xtoken + (lambda (str kw) + (if (and (eq? 0 (##sys#size str)) + (not (char=? c #\|))) + (##sys#read-error port "empty keyword") + (build-keyword str))))))) ((#\%) (build-symbol (##sys#string-append "#" (r-token))) ) ((#\+) @@ -3215,6 +3221,12 @@ EOF (or (fx<= c 32) (memq chr special-characters) ) ) ) + (define (outsym port sym) + (let ((str (##sys#symbol->string sym))) + (if (or (not readable) (sym-is-readable? str)) + (outstr port str) + (outreadablesym port str)))) + (define (outreadablesym port str) (let ((len (##sys#size str))) (outchr port #\|) @@ -3289,27 +3301,21 @@ EOF ((not (##core#inline "C_blockp" x)) (outstr port "#<invalid immediate object>")) ((##core#inline "C_forwardedp" x) (outstr port "#<invalid forwarded object>")) ((##core#inline "C_symbolp" x) - (cond [(fx= 0 (##sys#byte (##sys#slot x 1) 0)) - (let ([str (##sys#symbol->string x)]) - (case ksp - [(#:prefix) - (outchr port #\:) - (outstr port str) ] - [(#:suffix) - (outstr port str) - (outchr port #\:) ] - [else - (outstr port "#:") - (outstr port str) ] ) ) ] - [(memq x '(#!optional #!key #!rest)) - (outstr port (##sys#slot x 1))] - [(##sys#qualified-symbol? x) - (outstr port (##sys#symbol->qualified-string x))] + (cond ((fx= 0 (##sys#byte (##sys#slot x 1) 0)) ; keyword + (case ksp + ((#:prefix) + (outchr port #\:) + (outsym port x)) + ((#:suffix) + (outsym port x) + (outchr port #\:)) + (else + (outstr port "#:") + (outsym port x)))) + ((##sys#qualified-symbol? x) + (outstr port (##sys#symbol->qualified-string x))) (else - (let ((str (##sys#symbol->string x))) - (if (or (not readable) (sym-is-readable? str)) - (outstr port str) - (outreadablesym port str) ) ) ) ) ) + (outsym port x)))) ((##sys#number? x) (outstr port (##sys#number->string x))) ((##core#inline "C_anypointerp" x) (outstr port (##sys#pointer->string x))) ((##core#inline "C_stringp" x) diff --git a/tests/library-tests.scm b/tests/library-tests.scm index eb9506ad..384ca402 100644 --- a/tests/library-tests.scm +++ b/tests/library-tests.scm @@ -322,11 +322,25 @@ (parameterize ((keyword-style #:suffix)) (assert (string=? "abc:" (symbol->string (with-input-from-string "|abc:|" read)))) - (assert (string=? "abc" (symbol->string (with-input-from-string "|abc|:" read))))) ; keyword + (assert (string=? "abc" (symbol->string (with-input-from-string "|abc|:" read)))) ; keyword + (let ((kw (with-input-from-string "|foo bar|:" read))) + (assert (eq? kw (with-input-from-string "#:|foo bar|" read))) + (assert (string=? "foo bar" (symbol->string kw))) + (assert (string=? "foo bar:" + (with-output-to-string (lambda () (display kw))))) + (assert (string=? "|foo bar|:" + (with-output-to-string (lambda () (write kw))))))) (parameterize ((keyword-style #:prefix)) (assert (string=? "abc" (symbol->string (with-input-from-string ":|abc|" read)))) - (assert (string=? ":abc" (symbol->string (with-input-from-string "|:abc|" read))))) + (assert (string=? ":abc" (symbol->string (with-input-from-string "|:abc|" read)))) + (let ((kw (with-input-from-string ":|foo bar|" read))) + (assert (eq? kw (with-input-from-string "#:|foo bar|" read))) + (assert (string=? "foo bar" (symbol->string kw))) + (assert (string=? ":foo bar" + (with-output-to-string (lambda () (display kw))))) + (assert (string=? ":|foo bar|" + (with-output-to-string (lambda () (write kw))))))) (assert (eq? '|#:| (string->symbol "#:"))) (assert-fail (with-input-from-string "#:" read)) ; empty keyword @@ -353,10 +367,29 @@ (assert (not (keyword? (with-input-from-string ":abc:" read)))) (assert (not (keyword? (with-input-from-string "abc:" read))))) -(assert (string=? ":" (symbol->string (with-input-from-string ":" read)))) -(assert (string=? ":" (symbol->string (with-input-from-string ":||" read)))) +(let ((colon-sym (with-input-from-string ":" read))) + (assert (symbol? colon-sym)) + (assert (not (keyword? colon-sym))) + (assert (string=? ":" (symbol->string colon-sym)))) + +;; The next two cases are a bit dubious. These could also be read as +;; keywords due to the literal quotation. +(let ((colon-sym (with-input-from-string ":||" read))) + (assert (symbol? colon-sym)) + (assert (not (keyword? colon-sym))) + (assert (string=? ":" (symbol->string colon-sym)))) + +(let ((colon-sym (with-input-from-string "||:" read))) + (assert (symbol? colon-sym)) + (assert (not (keyword? colon-sym))) + (assert (string=? ":" (symbol->string colon-sym)))) + (assert-fail (with-input-from-string "#:" read)) +(let ((empty-kw (with-input-from-string "#:||" read))) + (assert (keyword? empty-kw)) + (assert (string=? "" (keyword->string empty-kw)))) + (assert (keyword? (with-input-from-string "42:" read))) (assert (keyword? (with-input-from-string ".:" read)))Trap