;;; "byte.scm" small integers, not necessarily chars.
; Copyright (c) 2001, 2002, 2003 Aubrey Jaffer
;
;Permission to copy this software, to modify it, to redistribute it,
;to distribute modified versions, and to use it for any purpose is
;granted, subject to the following restrictions and understandings.
;
;1.  Any copy made of this software must include this copyright notice
;in full.
;
;2.  I have made no warranty or representation that the operation of
;this software will be error-free, and I am under no obligation to
;provide any services, by way of maintenance, update, or otherwise.
;
;3.  In conjunction with products arising from the use of this
;material, there shall be no use of my name in any advertising,
;promotional, or sales literature without prior written consent in
;each case.

;;@code{(require 'byte)}
;;@ftindex byte
;;
;;@noindent
;;Some algorithms are expressed in terms of arrays of small integers.
;;Using Scheme strings to implement these arrays is not portable vis-a-vis
;;the correspondence between integers and characters and non-ascii
;;character sets.  These functions abstract the notion of a @dfn{byte}.
;;@cindex byte

;;@body
;;@2 must be a valid index of @1.  @0 returns byte @2 of @1 using
;;zero-origin indexing.
(define (byte-ref bytes k) (char->integer (string-ref bytes k)))

;;@body
;;@2 must be a valid index of @1, and @var{byte} must be a small
;;nonnegative integer.  @0 stores @var{byte} in element @2 of @1 and
;;returns an unspecified value.  @c <!>
(define (byte-set! bytes k byte) (string-set! bytes k (integer->char byte)))

;;@args k byte
;;@args k
;;@0 returns a newly allocated byte-array of length @1.  If @2 is
;;given, then all elements of the byte-array are initialized to @2,
;;otherwise the contents of the byte-array are unspecified.
(define (make-bytes len . opt)
  (if (null? opt) (make-string len)
      (make-string len (integer->char (car opt)))))

;;@args bytes
;;@0 returns length of byte-array @1.
(define bytes-length string-length)

;;@args byte @dots{}
;;Returns a newly allocated byte-array composed of the small
;;nonnegative arguments.
(define (bytes . args) (list->bytes args))

;;@args bytes
;;@0 returns a newly allocated list of the bytes that make up the
;;given byte-array.
(define (bytes->list bts) (map char->integer (string->list bts)))

;;@args bytes
;;@0 returns a newly allocated byte-array formed from the small
;;nonnegative integers in the list @1.
(define (list->bytes lst) (list->string (map integer->char lst)))

;;@noindent
;;@code{Bytes->list} and @code{list->bytes} are inverses so far as
;;@code{equal?} is concerned.
;;@findex equal?

;;@args bytes
;;Returns a newly allocated copy of the given @1.
(define bytes-copy string-copy)

;;@body
;;Reverses the order of byte-array @1.
(define (bytes-reverse! bytes)
  (do ((idx 0 (+ 1 idx))
       (xdi (+ -1 (bytes-length bytes)) (+ -1 xdi)))
      ((>= idx xdi) bytes)
    (let ((tmp (byte-ref bytes idx)))
      (byte-set! bytes idx (byte-ref bytes xdi))
      (byte-set! bytes xdi tmp))))

;;@body
;;Returns a newly allocated bytes-array consisting of the elements of
;;@1 in reverse order.
(define (bytes-reverse bytes)
  (bytes-reverse! (bytes-copy bytes)))

;;@noindent
;;@cindex binary
;;Input and output of bytes should be with ports opened in @dfn{binary}
;;mode (@pxref{Input/Output}).  Calling @code{open-file} with @r{'rb} or
;;@findex open-file
;;@r{'wb} modes argument will return a binary port if the Scheme
;;implementation supports it.

;;@args byte port
;;@args byte
;;Writes the byte @1 (not an external representation of the byte) to
;;the given @2 and returns an unspecified value.  The @2 argument may
;;be omitted, in which case it defaults to the value returned by
;;@code{current-output-port}.
;;@findex current-output-port
(define (write-byte byt . opt) (apply write-char (integer->char byt) opt))

;;@args port
;;@args
;;Returns the next byte available from the input @1, updating the @1
;;to point to the following byte.  If no more bytes are available, an
;;end-of-file object is returned.  @1 may be omitted, in which case it
;;defaults to the value returned by @code{current-input-port}.
;;@findex current-input-port
(define (read-byte . opt)
  (let ((c (apply read-char opt)))
    (if (eof-object? c) c (char->integer c))))

;;@noindent
;;When reading and writing binary numbers with @code{read-bytes} and
;;@code{write-bytes}, the sign of the length argument determines the
;;endianness (order) of bytes.  Positive treats them as big-endian,
;;the first byte input or output is highest order.  Negative treats
;;them as little-endian, the first byte input or output is the lowest
;;order.
;;
;;@noindent
;;Once read in, SLIB treats byte sequences as big-endian.  The
;;multi-byte sequences produced and used by number conversion routines
;;@pxref{Byte/Number Conversions} are always big-endian.

;;@args n port
;;@args n
;;@0 returns a newly allocated bytes-array filled with
;;@code{(abs @var{n})} bytes read from @2.  If @1 is positive, then
;;the first byte read is stored at index 0; otherwise the last byte
;;read is stored at index 0.  Note that the length of the returned
;;string will be less than @code{(abs @var{n})} if @2 reaches
;;end-of-file.
;;
;;@2 may be omitted, in which case it defaults to the value returned
;;by @code{current-input-port}.
(define (read-bytes n . port)
  (let* ((len (abs n))
	 (byts (make-bytes len))
	 (cnt (if (positive? n)
		  (apply substring-read! byts 0 n port)
		  (apply substring-read! byts (- n) 0 port))))
    (if (= cnt len)
	byts
	(if (positive? n)
	    (substring byts 0 cnt)
	    (substring byts (- len cnt) len)))))

;;@args bytes n port
;;@args bytes n
;;@0 writes @code{(abs @var{n})} bytes to output-port @3.  If @2 is
;;positive, then the first byte written is index 0 of @1; otherwise
;;the last byte written is index 0 of @1.  @0 returns an unspecified
;;value.
;;
;;@3 may be omitted, in which case it defaults to the value returned
;;by @code{current-output-port}.
(define (write-bytes bytes n . port)
  (if (positive? n)
      (apply substring-write bytes 0 n port)
      (apply substring-write bytes (- n) 0 port)))

;;@noindent
;;@code{substring-read!} and @code{substring-write} provide
;;lower-level procedures for reading and writing blocks of bytes.  The
;;relative size of @var{start} and @var{end} determines the order of
;;writing.

;;@args string start end port
;;@args string start end
;;Fills @1 with up to @code{(abs (- @var{start} @var{end}))} bytes
;;read from @4.  The first byte read is stored at index @1.
;;@0 returns the number of bytes read.
;;
;;@4 may be omitted, in which case it defaults to the value returned
;;by @code{current-input-port}.
(define (substring-read! string start end . port)
  (if (>= end start)
      (do ((idx start (+ 1 idx)))
	  ((>= idx end) idx)
	(let ((byt (apply read-byte port)))
	  (cond ((eof-object? byt)
		 (set! idx (+ -1 idx))
		 (set! end idx))
		(else (byte-set! string idx byt)))))
      (do ((idx (+ -1 start) (+ -1 idx))
	   (cnt 0 (+ 1 cnt)))
	  ((< idx end) cnt)
	(let ((byt (apply read-byte port)))
	  (cond ((eof-object? byt)
		 (set! idx start)
		 (set! cnt (+ -1 cnt)))
		(else (byte-set! string idx byt)))))))

;;@args string start end port
;;@args string start end
;;@0 writes @code{(abs (- @var{start} @var{end}))} bytes to
;;output-port @4.  The first byte written is index @2 of @1.  @0
;;returns the number of bytes written.
;;
;;@4 may be omitted, in which case it defaults to the value returned
;;by @code{current-output-port}.
(define (substring-write string start end . port)
  (if (>= end start)
      (do ((idx start (+ 1 idx)))
	  ((>= idx end) (- end start))
	(apply write-byte (byte-ref string idx) port))
      (do ((idx (+ -1 start) (+ -1 idx)))
	  ((< idx end) (- start end))
	(apply write-byte (byte-ref string idx) port))))
