chibi-scheme/lib/chibi/binary-record.scm
2015-01-26 08:06:59 +09:00

300 lines
11 KiB
Scheme

(define (read-u16/be in)
(let* ((i (read-u8 in))
(j (read-u8 in)))
(if (eof-object? j)
(error "end of input")
(+ (arithmetic-shift i 8) j))))
(define (read-u16/le in)
(let* ((i (read-u8 in))
(j (read-u8 in)))
(if (eof-object? j)
(error "end of input")
(+ (arithmetic-shift j 8) i))))
;; Record types with user-specified binary formats.
;; A work in progress, but sufficient for tar files.
(define (assert-read-u8 in i)
(let ((i2 (read-u8 in)))
(if (not (eqv? i i2))
(error "unexpected value: " i i2)
i2)))
(define (assert-read-char in ch)
(let ((ch2 (read-char in)))
(if (not (eqv? ch ch2))
(error "unexpected value: " ch ch2)
ch2)))
(define (assert-read-string in s)
(let ((s2 (read-string (string-length s) in)))
(if (not (equal? s s2))
(error "unexpected value: " s s2)
s2)))
(define (assert-read-bytevector in bv)
(let ((bv2 (read-bytevector (bytevector-length bv) in)))
(if (not (equal? bv bv2))
(error "unexpected value: " bv bv2)
bv2)))
(define (assert-read-integer in len radix)
(let* ((s (string-trim (read-string len in)
(lambda (ch) (or (eqv? ch #\space) (eqv? ch #\null)))))
(n (if (equal? s "") 0 (string->number s radix))))
(or n (error "invalid number syntax: " s))))
(define (read-padded-string in len pad)
(string-trim-right (read-string len in) pad))
(define (expand-read rename in spec)
(case (car spec)
((literal)
(let ((val (cadr spec)))
(cond ((integer? val) `(,(rename 'assert-read-u8) ,in ,val))
((char? val) `(,(rename 'assert-read-char) ,in ,val))
((string? val) `(,(rename 'assert-read-string) ,in ,val))
((bytevector? val) `(,(rename 'assert-read-bytevector) ,in ,val))
(else (error "unknown binary literal: " val)))))
((u8)
`(,(rename 'read-u8) ,in))
((u16/be)
`(,(rename 'read-u16/be) ,in))
((u16/le)
`(,(rename 'read-u16/le) ,in))
((octal)
`(,(rename 'assert-read-integer) ,in ,(cadr spec) 8))
((decimal)
`(,(rename 'assert-read-integer) ,in ,(cadr spec) 10))
((hexadecimal)
`(,(rename 'assert-read-integer) ,in ,(cadr spec) 16))
((fixed-string)
(let ((len (cadr spec)))
`(,(rename 'read-string) ,len ,in)))
((padded-string)
(let ((len (cadr spec))
(pad (if (pair? (cddr spec)) (car (cddr spec)) #\null)))
`(,(rename 'read-padded-string) ,in ,len ,pad)))
(else
(error "unknown binary format: " spec))))
(define (string-pad-left str len . o)
(let ((diff (- len (string-length str)))
(pad-ch (if (pair? o) (car o) #\space)))
(if (positive? diff)
(string-append (make-string diff pad-ch) str)
str)))
(define (string-pad-right str len . o)
(let ((diff (- len (string-length str)))
(pad-ch (if (pair? o) (car o) #\space)))
(if (positive? diff)
(string-append str (make-string diff pad-ch))
str)))
(define (write-padded-integer out n radix len left-pad-ch right-pad-ch)
(let ((s (string-pad-left (number->string n radix) (- len 1) left-pad-ch)))
(cond
((>= (string-length s) len)
(error "number too large for width" n radix len))
(else
(write-string s out)
(write-char right-pad-ch out)))))
(define (write-u16/be n out)
(write-u8 (arithmetic-shift n -8) out)
(write-u8 (bitwise-and n #xFF) out))
(define (write-u16/le n out)
(write-u8 (bitwise-and n #xFF) out)
(write-u8 (arithmetic-shift n -8) out))
(define (expand-write rename out val spec)
(let ((_if (rename 'if))
(_not (rename 'not))
(_let (rename 'let))
(_string-length (rename 'string-length))
(_write-string (rename 'write-string))
(_write-bytevector (rename 'write-bytevector))
(_error (rename 'error))
(_> (rename '>))
(_= (rename '=)))
(case (car spec)
((literal)
(let ((val (cadr spec)))
(cond ((integer? val) `(,(rename 'write-u8) ,val ,out))
((char? val) `(,(rename 'write-char) ,val ,out))
((string? val) `(,_write-string ,val ,out))
((bytevector? val) `(,_write-bytevector ,val ,out))
(else (error "unknown binary literal: " val)))))
((u8)
`(,(rename 'write-u8) ,val ,out))
((u16/be)
`(,(rename 'write-u16/be) ,val ,out))
((u16/le)
`(,(rename 'write-u16/le) ,val ,out))
((octal)
`(,(rename 'write-padded-integer) ,out ,val 8 ,(cadr spec) #\0 #\null))
((decimal)
`(,(rename 'write-padded-integer) ,out ,val 10 ,(cadr spec) #\0 #\null))
((hexadecimal)
`(,(rename 'write-padded-integer) ,out ,val 16 ,(cadr spec) #\0 #\null))
((fixed-string)
(let ((len (cadr spec)))
`(,_if (,_not (,_= ,len (,_string-length ,val)))
(,_error "wrong field length: " ,val ,len)
(,_write-string ,val ,out))))
((padded-string)
(let ((len (cadr spec))
(pad (if (pair? (cddr spec)) (car (cddr spec)) #\null)))
`(,_let ((l (,_string-length ,val)))
(,_if (,_> l ,len)
(,_error "field too large: " ,val ,len)
(,_write-string (,(rename 'string-pad-right) ,val ,len ,pad)
,out)))))
(else
(error "unknown binary format: " spec)))))
(define (expand-assert rename spec x v)
(let ((_if (rename 'if))
(_not (rename 'not))
(_error (rename 'error))
(_integer? (rename 'integer?))
(_string? (rename 'string?))
(_string-length (rename 'string-length))
(_> (rename '>)))
(case (car spec)
((literal) #t)
((u8 u16/be u16/le octal decimal hexadecimal)
`(,_if (,_not (,_integer? ,v))
(,_error "expected an integer" ,v)))
((fixed-string padded-string)
(let ((len (cadr spec)))
`(,_if (,_not (,_string? ,v))
(,_error "expected a string" ,v)
(,_if (,_> (,_string-length ,v) ,len)
(,_error "string too long" ,v ,len)))))
(else (error "unknown binary format: " spec)))))
(define (expand-default rename spec)
(case (car spec)
((literal) (cadr spec))
((u8 u16/be u16/le octal decimal hexadecimal) 0)
((fixed-string) (make-string (cadr spec) #\space))
((padded-string) "")
(else (error "unknown binary format: " spec))))
(define (param-ref ls key . o)
(cond ((assq key ls) => cadr) ((pair? o) (car o)) (else #f)))
(define (symbol-append a b)
(string->symbol (string-append (symbol->string a) (symbol->string b))))
(define-record-type Field
(make-field name get set raw-set spec)
field?
(name field-name)
(get field-get)
(set field-set)
(raw-set field-raw-set)
(spec field-spec))
(define (extract-fields type ls)
(let lp ((ls ls) (res '()))
(cond
((null? ls)
(reverse res))
((not (pair? (car ls)))
(lp (cdr ls) (cons (make-field '_ #f #f #f `(literal ,(car ls))) res)))
(else
(let* ((name (caar ls))
(get (or (param-ref (car ls) 'getter)
(and (not (eq? name '_))
(symbol-append type (symbol-append '- name)))))
(set (or (param-ref (car ls) 'setter)
(and (not (eq? name '_))
(symbol-append (symbol-append type '-)
(symbol-append name '-set!)))))
(raw-set (and set (symbol-append '% set)))
(spec0 (cadr (car ls)))
(spec (if (pair? spec0) spec0 (list spec0))))
(lp (cdr ls) (cons (make-field name get set raw-set spec) res)))))))
(define-syntax define-binary-record-type
(er-macro-transformer
(lambda (expr rename compare)
(let ((name (cadr expr))
(ls (cddr expr)))
(if (not (and (identifier? name) (every list? ls)))
(error "invalid syntax: " expr))
(let* ((type (or (param-ref ls 'type) (symbol-append 'type- name)))
(pred (or (param-ref ls 'predicate) (symbol-append name '?)))
(make (or (param-ref ls 'make) (symbol-append 'make- name)))
(make-spec (if (pair? make) make (list make)))
(%make (rename (symbol-append '% (car make-spec))))
(%%make (rename (symbol-append '%% (car make-spec))))
(reader (or (param-ref ls 'read) (symbol-append 'read- name)))
(writer (or (param-ref ls 'write) (symbol-append 'write- name)))
(block (assq 'block ls))
(_begin (rename 'begin))
(_define (rename 'define))
(_define-record-type (rename 'define-record-type))
(_let (rename 'let)))
(if (not block)
(error "missing binary record block: " expr))
(let* ((fields (extract-fields name (cdr block)))
(named-fields (filter (lambda (f) (not (eq? '_ (field-name f))))
fields)))
`(,_begin
(,_define ,name ',ls)
(,_define-record-type
,type (,%%make) ,pred
,@(map
(lambda (f)
`(,(field-name f) ,(field-get f) ,(field-raw-set f)))
named-fields))
,@(map
(lambda (f)
`(,_define (,(field-set f) x v)
,(expand-assert rename (field-spec f) 'x 'v)
(,(field-raw-set f) x v)))
named-fields)
(,_define (,%make)
(let ((res (,%%make)))
,@(map
(lambda (f)
`(,(field-raw-set f)
res
,(expand-default rename (field-spec f))))
named-fields)
res))
(,_define ,make-spec
(,_let ((res (,%make)))
,@(map
(lambda (x)
(let ((field (find (lambda (f) (eq? x (field-name f)))
fields)))
`(,(field-set field) res ,x)))
(cdr make-spec))
res))
(,_define (,reader in)
(,_let ((res (,%make)))
,@(map
(lambda (f)
(if (eq? '_ (field-name f))
(expand-read rename 'in (field-spec f))
`(,(field-set f)
res
,(expand-read rename 'in (field-spec f)))))
fields)
res))
(,_define (,writer x out)
,@(map
(lambda (f)
(expand-write rename
'out
`(,(field-get f) x)
(field-spec f)))
fields)))))))))