~ chicken-core (master) 72483b2401a1a691be8f3c7c314740d79cdaa8d0


commit 72483b2401a1a691be8f3c7c314740d79cdaa8d0
Author:     Kristian Lein-Mathisen <kristianlein@gmail.com>
AuthorDate: Mon Aug 31 15:22:12 2026 +0200
Commit:     felix <felix@call-with-current-continuation.org>
CommitDate: Mon Aug 31 20:32:38 2026 +0200

    reader: introduce hex bytevector literals
    
    bringing a CHICKEN 5 blob look-alike syntax back. see the added tests
    for syntax. blob literals need special care because leading zeros
    affect the result, and hence we can't re-use the existing reader for
    numbers.
    
    read-bytevector-literal is similar to CHICKEN 5's ##sys#read-bytevector-literal
    but differs in that it:
    
    - returns a list of fixnums / bytes in order
    - only allows 2-digit hex entries or multiples of 2-digit hex entries
      for clarity
    
    from ddf6d90e1a9e29246084cfff4a8544f2c25089e7 in C5 we have these
    tests:
    
        (assert (equal? '#${a} '#${0a}))
        (assert (equal? '#${ab cd} '#${abcd}))
        (assert (equal? '#${ab c} '#${ab0c}))
        (assert (equal? '#${abc} '#${ab0c}))
        (assert (equal? '#${a b c} '#${0a0b0c}))
    
        ;; self-evaluating
        (assert (equal? '#${a} #${a}))
        (assert (equal? '#${abcd} #${abcd}))
        (assert (equal? '#${abc} #${abc}))
    
    this test covers some of those cases. it adds error checking against
    single-nibbles, but does not test for self-evaluating (do we need that?)

diff --git a/library.scm b/library.scm
index 31f12cac..a1db238f 100644
--- a/library.scm
+++ b/library.scm
@@ -5379,10 +5379,42 @@ EOF
              (else (fail sym))))))
     (else (fail char))))
 
+;; returns a (potentially large) list of numbers (bytes) in
+;; order. ignores whitespace.
+(define (##sys#read-hex-literal port) ;; { 00ff11 ff } => #u8(0 255 17 255)
+  (define (hex c)
+    (cond ((and (char>=? c #\a) (char<=? c #\f)) (fx- (char->integer c) 87)) ;; (- (char->integer #\a) 10)
+          ((and (char>=? c #\A) (char<=? c #\F)) (fx- (char->integer c) 55)) ;; (- (char->integer #\A) 10)
+	  ((and (char>=? c #\0) (char<=? c #\9)) (fx- (char->integer c) 48)) ;; (char->integer #\0)
+	  (else (##sys#read-error port "invalid hex-code in bytecode literal" c))))
+
+  (unless (eq? #\{ (##sys#read-char-0 port)) (##sys#read-error port "internal error"))
+  (let ((first #f))
+    (let loop ((h #f) (last #f))
+      (let ((c (##sys#read-char-0 port)))
+        (cond ((eof-object? c)
+	       (##sys#read-error port "unexpected end of hex bytevector literal"))
+	      ((char=? #\} c)
+	       (if h
+		   (##sys#read-error port "odd-numbered hex bytevector literal")
+		   (or first '())))
+	      ((char-whitespace? c)
+	       (if h
+		   (##sys#read-error port "odd-numbered hex bytevector literal")
+		   (loop #f last)))
+	      (h
+               (let ((node (cons (fxior (fxshl h 4) (hex c)) '())))
+                 (if first
+                     (##sys#setslot last 1 node)
+                     (set! first node))
+                 (loop #f node)))
+	      (else (loop (hex c) last)))))))
+
 (define (##sys#read-numvector-data port)
   (let ((c (##sys#peek-char-0 port)))
     (case c
       ((#\( #\") (##sys#read port ##sys#default-read-info-hook))
+      ((#\{) (##sys#read-hex-literal port))
       (else (##sys#read-error port "invalid numeric vector syntax" c)))))
 
 (define (##sys#canonicalize-number-list! lst1)
diff --git a/tests/library-tests.scm b/tests/library-tests.scm
index 38ab88dd..e63aeb6a 100644
--- a/tests/library-tests.scm
+++ b/tests/library-tests.scm
@@ -617,7 +617,8 @@
 (assert-fail (with-input-from-string "\"" read))
 (assert-fail (with-input-from-string "#|" read))
 (assert-fail (with-input-from-string "#(" read))
-(assert-fail (with-input-from-string "#${" read))
+(assert-fail (with-input-from-string "#u8(" read))
+(assert-fail (with-input-from-string "#u8{" read))
 (assert-fail (with-input-from-string "\\" read))
 (assert-fail (with-input-from-string "|\\" read))
 (assert-fail (with-input-from-string "\"\\" read))
@@ -677,6 +678,22 @@ A
 (assert (string-ci<? "foo\x00;a" "foo\x00;B"))
 (assert (string-ci>? "foo\x00;b" "foo\x00;A"))
 
+;; hex bytevector literals
+(assert-fail (with-input-from-string "#u8{f}" read))
+(assert-fail (with-input-from-string "#u8{ff0}" read))
+(assert-fail (with-input-from-string "#u8{1}" read))
+(assert-fail (with-input-from-string "#u8{-1}" read))
+(assert-fail (with-input-from-string "#u8{ -01 }" read))
+(assert-fail (with-input-from-string "#u8{F11223344556677889900}" read))
+
+(assert (equal? '#u8{}   '#u8()))
+(assert (equal? '#u8{01} '#u8(1)))
+(assert (equal? '#u8{00 01 10 11} '#u8(0 1 16 17)))
+(assert (equal? '#u8{af fa FA AF} '#u8(175 250 250 175)))
+(assert (equal? '#u8{0001   0203  0aff} '#u8(0 1 2 3 10 255)))
+(assert (equal? '#u8{0001   0203  0aff} '#u8(0 1 2 3 10 255)))
+(assert (equal? '#u8{000102030aff}      '#u8(0 1 2 3 10 255)))
+
 ;; reported by Nils Holm (#1534)
 ;; https://groups.google.com/group/comp.lang.scheme/t/6b8be06b84b39a7
 (assert (not (string-ci<=? "test" "tes")))
Trap