spec: bytevectors (make-bytevector/u8-ref/u8-set!/utf8->string/etc)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 19:16:02 +00:00
parent 24e1a862fb
commit a381154507
5 changed files with 486 additions and 3 deletions

View File

@@ -1252,3 +1252,77 @@
:params ((re :as regexp) (str :as string))
:returns "list"
:doc "Split str on every match of re; returns list of strings.")
(define-module :stdlib.bytevectors)
(define-primitive
"make-bytevector"
:params (n &rest fill)
:returns "bytevector"
:doc "Create a bytevector of n bytes, all initialised to fill (default 0).")
(define-primitive
"bytevector?"
:params (v)
:returns "boolean"
:doc "True if v is a bytevector.")
(define-primitive
"bytevector-length"
:params ((bv :as bytevector))
:returns "number"
:doc "Number of bytes in bv.")
(define-primitive
"bytevector-u8-ref"
:params ((bv :as bytevector) (i :as number))
:returns "number"
:doc "Byte value 0-255 at index i.")
(define-primitive
"bytevector-u8-set!"
:params ((bv :as bytevector) (i :as number) (byte :as number))
:returns "nil"
:doc "Set byte at index i to byte 0-255. Mutates bv.")
(define-primitive
"bytevector-copy"
:params ((bv :as bytevector) &rest bounds)
:returns "bytevector"
:doc "Fresh copy of bv, optionally sliced to [start, end).")
(define-primitive
"bytevector-copy!"
:params ((dst :as bytevector) (at :as number) (src :as bytevector) &rest bounds)
:returns "nil"
:doc "Copy bytes from src[start..end) into dst starting at at. Mutates dst.")
(define-primitive
"bytevector-append"
:params (&rest bvs)
:returns "bytevector"
:doc "Concatenate bytevectors into a new bytevector.")
(define-primitive
"utf8->string"
:params ((bv :as bytevector) &rest bounds)
:returns "string"
:doc "Decode bv[start..end) as UTF-8 and return the string.")
(define-primitive
"string->utf8"
:params ((s :as string) &rest bounds)
:returns "bytevector"
:doc "Encode s[start..end) as UTF-8 and return a bytevector.")
(define-primitive
"bytevector->list"
:params ((bv :as bytevector))
:returns "list"
:doc "Convert bytevector to a list of byte integers.")
(define-primitive
"list->bytevector"
:params ((lst :as list))
:returns "bytevector"
:doc "Build a bytevector from a list of byte integers 0-255.")

View File

@@ -0,0 +1,236 @@
;; ==========================================================================
;; test-bytevectors.sx — Tests for bytevector primitives
;; ==========================================================================
;; --------------------------------------------------------------------------
;; make-bytevector / bytevector?
;; --------------------------------------------------------------------------
(defsuite
"bv:create"
(deftest
"make-bytevector returns bytevector"
(assert (bytevector? (make-bytevector 4))))
(deftest
"make-bytevector zeroes by default"
(let
((bv (make-bytevector 3)))
(assert
(and
(= (bytevector-u8-ref bv 0) 0)
(= (bytevector-u8-ref bv 1) 0)
(= (bytevector-u8-ref bv 2) 0)))))
(deftest
"make-bytevector with fill"
(let
((bv (make-bytevector 3 42)))
(assert
(and
(= (bytevector-u8-ref bv 0) 42)
(= (bytevector-u8-ref bv 1) 42)
(= (bytevector-u8-ref bv 2) 42)))))
(deftest
"make-bytevector length 0"
(assert= (bytevector-length (make-bytevector 0)) 0))
(deftest
"bytevector? true for bytevector"
(assert (bytevector? (make-bytevector 2))))
(deftest
"bytevector? false for string"
(assert (not (bytevector? "hello"))))
(deftest "bytevector? false for nil" (assert (not (bytevector? nil))))
(deftest
"bytevector? false for list"
(assert (not (bytevector? (list 1 2 3))))))
;; --------------------------------------------------------------------------
;; bytevector-length / u8-ref / u8-set!
;; --------------------------------------------------------------------------
(defsuite
"bv:access"
(deftest
"bytevector-length"
(assert= (bytevector-length (make-bytevector 5)) 5))
(deftest
"u8-ref reads fill byte"
(assert=
(bytevector-u8-ref (make-bytevector 4 99) 2)
99))
(deftest
"u8-set! mutates"
(let
((bv (make-bytevector 3 0)))
(bytevector-u8-set! bv 1 200)
(assert= (bytevector-u8-ref bv 1) 200)))
(deftest
"u8-set! boundary byte 255"
(let
((bv (make-bytevector 1 0)))
(bytevector-u8-set! bv 0 255)
(assert= (bytevector-u8-ref bv 0) 255)))
(deftest
"u8-set! byte 0"
(let
((bv (make-bytevector 1 255)))
(bytevector-u8-set! bv 0 0)
(assert= (bytevector-u8-ref bv 0) 0))))
;; --------------------------------------------------------------------------
;; bytevector-copy
;; --------------------------------------------------------------------------
(defsuite
"bv:copy"
(deftest
"copy produces equal content"
(let
((bv (make-bytevector 3 7)))
(let
((bv2 (bytevector-copy bv)))
(assert
(and
(= (bytevector-u8-ref bv2 0) 7)
(= (bytevector-u8-ref bv2 1) 7)
(= (bytevector-u8-ref bv2 2) 7))))))
(deftest
"copy is independent"
(let
((bv (make-bytevector 2 0)))
(let
((bv2 (bytevector-copy bv)))
(bytevector-u8-set! bv2 0 99)
(assert= (bytevector-u8-ref bv 0) 0))))
(deftest
"copy with start"
(let
((bv (list->bytevector (list 10 20 30 40))))
(let
((bv2 (bytevector-copy bv 2)))
(assert
(and
(= (bytevector-length bv2) 2)
(= (bytevector-u8-ref bv2 0) 30))))))
(deftest
"copy with start and end"
(let
((bv (list->bytevector (list 10 20 30 40))))
(let
((bv2 (bytevector-copy bv 1 3)))
(assert
(and
(= (bytevector-length bv2) 2)
(= (bytevector-u8-ref bv2 0) 20)
(= (bytevector-u8-ref bv2 1) 30)))))))
;; --------------------------------------------------------------------------
;; bytevector-copy!
;; --------------------------------------------------------------------------
(defsuite
"bv:copy-bang"
(deftest
"copy! overwrites dst region"
(let
((dst (make-bytevector 4 0)))
(let
((src (list->bytevector (list 1 2 3))))
(bytevector-copy! dst 1 src)
(assert
(and
(= (bytevector-u8-ref dst 0) 0)
(= (bytevector-u8-ref dst 1) 1)
(= (bytevector-u8-ref dst 2) 2)
(= (bytevector-u8-ref dst 3) 3))))))
(deftest
"copy! with src bounds"
(let
((dst (make-bytevector 2 0)))
(let
((src (list->bytevector (list 10 20 30 40))))
(bytevector-copy! dst 0 src 1 3)
(assert
(and
(= (bytevector-u8-ref dst 0) 20)
(= (bytevector-u8-ref dst 1) 30)))))))
;; --------------------------------------------------------------------------
;; bytevector-append
;; --------------------------------------------------------------------------
(defsuite
"bv:append"
(deftest
"append two bytevectors"
(let
((bv (bytevector-append (list->bytevector (list 1 2)) (list->bytevector (list 3 4)))))
(assert
(and
(= (bytevector-length bv) 4)
(= (bytevector-u8-ref bv 0) 1)
(= (bytevector-u8-ref bv 3) 4)))))
(deftest
"append three bytevectors"
(let
((bv (bytevector-append (list->bytevector (list 1)) (list->bytevector (list 2)) (list->bytevector (list 3)))))
(assert= (bytevector-length bv) 3)))
(deftest
"append empty"
(assert=
(bytevector-length
(bytevector-append
(make-bytevector 0)
(make-bytevector 0)))
0)))
;; --------------------------------------------------------------------------
;; list->bytevector / bytevector->list
;; --------------------------------------------------------------------------
(defsuite
"bv:conversion"
(deftest
"list->bytevector roundtrip"
(let
((lst (list 10 20 30)))
(assert= (bytevector->list (list->bytevector lst)) lst)))
(deftest
"list->bytevector empty"
(assert= (bytevector-length (list->bytevector nil)) 0))
(deftest
"bytevector->list from make-bytevector"
(let
((lst (bytevector->list (make-bytevector 3 5))))
(assert= lst (list 5 5 5)))))
;; --------------------------------------------------------------------------
;; utf8 roundtrip
;; --------------------------------------------------------------------------
(defsuite
"bv:utf8"
(deftest
"string->utf8 returns bytevector"
(assert (bytevector? (string->utf8 "hello"))))
(deftest
"string->utf8 length"
(assert= (bytevector-length (string->utf8 "abc")) 3))
(deftest
"utf8->string roundtrip"
(assert= (utf8->string (string->utf8 "hello")) "hello"))
(deftest
"utf8->string with slice"
(let
((bv (string->utf8 "hello")))
(assert= (utf8->string bv 1 4) "ell")))
(deftest
"string->utf8 with start"
(assert= (utf8->string (string->utf8 "hello" 2)) "llo"))
(deftest
"string->utf8 with start and end"
(assert=
(utf8->string (string->utf8 "hello" 1 4))
"ell"))
(deftest
"empty string round-trips"
(assert= (utf8->string (string->utf8 "")) "")))