|
;;; needs schema.scm |
|
;;; |
|
;;; some inspiration taken from |
|
;;; https://github.com/confluentinc/avro-c-packaging/blob/master/src/encoding_binary.c#L67-L79 |
|
(import test) |
|
|
|
(define-syntax wots |
|
(syntax-rules () |
|
((_ body ...) (with-output-to-string (lambda () body ...))))) |
|
|
|
|
|
(define-syntax wifs |
|
(syntax-rules () |
|
((_ str body ...) (with-input-from-string str (lambda () body ...))))) |
|
|
|
|
|
(define (zigzag n) |
|
(bitwise-xor (arithmetic-shift n 1) |
|
(arithmetic-shift n -63))) |
|
|
|
(define (unzigzag n) |
|
(bitwise-xor (- (bitwise-and n 1)) |
|
(arithmetic-shift n -1))) |
|
|
|
(test-group |
|
"zigzag" |
|
(test 0 (unzigzag (zigzag 0))) |
|
(test -1 (unzigzag (zigzag -1))) |
|
(test 1 (unzigzag (zigzag 1))) |
|
(test -2 (unzigzag (zigzag -2))) |
|
(test 2 (unzigzag (zigzag 2))) |
|
(test -3 (unzigzag (zigzag -3))) |
|
(test 3 (unzigzag (zigzag 3)))) |
|
|
|
;; primitive types |
|
(define (write-vint n) |
|
(let loop ((n (zigzag n))) |
|
(if (>= n #b10000000) |
|
(begin (write-byte (bitwise-ior #b10000000 (bitwise-and n #b01111111))) |
|
(loop (arithmetic-shift n -7))) |
|
(write-byte n)))) |
|
|
|
(test-group |
|
"write-vint" |
|
(test "\x00" (wots (write-vint 0))) |
|
(test "\x01" (wots (write-vint -1))) |
|
(test "\x02" (wots (write-vint 1))) |
|
(test "\x03" (wots (write-vint -2))) |
|
(test "\x04" (wots (write-vint 2))) |
|
(test "\x05" (wots (write-vint -3))) |
|
(test "\x06" (wots (write-vint 3)))) |
|
|
|
(define (read-vint #!optional port) |
|
(set! port (or port (current-input-port))) |
|
(let loop ((n 0) |
|
(shift 0)) |
|
(let ((byte (read-byte port))) |
|
(if (eof-object? byte) |
|
(if (= shift 0) byte (error "premature eof while reading vint")) |
|
(let ((n (bitwise-ior n (arithmetic-shift |
|
(bitwise-and #b01111111 byte) shift)))) |
|
(if (zero? (bitwise-and #b10000000 byte)) |
|
(unzigzag n) |
|
(loop n (+ shift 7)))))))) |
|
|
|
(test-group |
|
"read-vint" |
|
(test 0 (wifs "\x00" (read-vint))) |
|
(test -1 (wifs "\x01" (read-vint))) |
|
(test 1 (wifs "\x02" (read-vint))) |
|
(test -2 (wifs "\x03" (read-vint))) |
|
(test 2 (wifs "\x04" (read-vint))) |
|
(test -3 (wifs "\x05" (read-vint))) |
|
(test 3 (wifs "\x06" (read-vint)))) |
|
|
|
(define (write-boolean v) (if v (write-byte 1) (write-byte 0))) |
|
(define (read-boolean) (if (zero? (read-byte)) #f #t)) |
|
|
|
(test-group |
|
"boolean" |
|
|
|
(test "\x00" (wots (write-boolean #f))) |
|
(test "\x01" (wots (write-boolean #t))) |
|
|
|
(test #f (wifs "\x00" (read-boolean))) |
|
(test #t (wifs "\x01" (read-boolean))) |
|
(test #t (wifs "\x04" (read-boolean)))) |
|
|
|
(define (write-float x) (error "TODO write float")) |
|
(define (read-float x) (error "TODO read float")) |
|
|
|
(define (write-double x) (error "TODO write double")) |
|
(define (read-double x) (error "TODO read double")) |
|
|
|
(define (write-bytes str) |
|
(write-vint (string-length str)) |
|
(write-string str)) |
|
|
|
(define (assert-size n str) |
|
(if (= n (number-of-bytes str)) |
|
str |
|
(error (conc "premature end of file, expecting " |
|
n " bytes but got " (number-of-bytes str)) |
|
str))) |
|
|
|
(define (read-bytes #!optional port) |
|
(set! port (or port (current-input-port))) |
|
(let ((len (read-vint port))) |
|
(assert-size len (read-string len port)))) |
|
|
|
;; complex types |
|
|
|
(define (write-enum x type) (error "TODO write enum")) |
|
(define (read-enum x type) (error "TODO read enum")) |
|
|
|
(define (write-array x type) (error "TODO write array")) |
|
(define (read-array x type) (error "TODO read array")) |
|
|
|
(define (write-pairs x write-type) |
|
(write-vint (length x)) |
|
(for-each (lambda (pair) |
|
(write-bytes (let ((j (car pair))) |
|
(if (symbol? j) |
|
(symbol->string j) |
|
j))) |
|
(write-type (cdr pair))) |
|
x)) |
|
|
|
(define (write-map x write-type) ;; TODO: support negative size with bytecount |
|
(write-pairs x write-type) |
|
(write-pairs '() #f)) |
|
|
|
(define (read-pairs count read-type #!optional port) |
|
(unless port (set! port (current-input-port))) |
|
(let loop ((count count) |
|
(result '())) |
|
(if (> count 0) |
|
(let ((key (read-bytes port)) |
|
(val (read-type port))) |
|
(loop (- count 1) (cons (cons (string->symbol key) val) result))) |
|
result))) |
|
|
|
(define (read-map read-type #!optional port) |
|
(unless port (set! port (current-input-port))) |
|
(let loop ((result '())) |
|
(let ((count (read-vint port))) |
|
(if (zero? count) |
|
(reverse result) ;; todo: do something less memory intensive here? |
|
(loop (append (read-pairs count read-type port) result)))))) |
|
|
|
(test-group |
|
"map" |
|
(let ((m `((one . "1") (two . "2")))) |
|
(test |
|
m |
|
(wifs (wots (write-map m write-bytes)) |
|
(read-map read-bytes))))) |
|
|
|
(define (write-union unioni x) (error "TODO ") |
|
(write-vint unioni)) |
|
(define (write-fixed x) (error "TODO")) |
|
|
|
;; container |
|
(define (write-magic) (display "Obj\x01")) |
|
|
|
|
|
|
|
(begin |
|
|
|
(define (schema-writer schema #!optional names) |
|
(cond ((equal? schema "int") write-vint) |
|
((equal? schema "long") write-vint) |
|
((equal? schema "bytes") write-bytes) |
|
((equal? schema "string") write-bytes) |
|
((equal? schema "float") write-float) |
|
((equal? schema "double") write-double) |
|
|
|
((pair? schema) |
|
(let ((type (alist-ref 'type schema))) |
|
(cond |
|
((equal? type "record") |
|
(let () |
|
|
|
(define (field-writer field) |
|
(schema-writer (cond ((alist-ref 'type field) => values) |
|
(else (error "field has no type" field))))) |
|
|
|
(let* ((rname (schema-record-name schema)) |
|
(fields (cond ((alist-ref 'fields schema) => vector->list) |
|
(else (error "record has no fields" schema)))) |
|
(writers (map field-writer fields))) |
|
(lambda (x) |
|
(unless (and (pair? x) |
|
(eq? rname (car x))) |
|
(error (conc "expecting record `(" rname " ...)") |
|
x schema)) |
|
|
|
(let ((x0 x)) |
|
|
|
(let loop ((writers writers) |
|
(x (cdr x0))) |
|
(if (pair? writers) |
|
(if (pair? x) |
|
(let ((w (car writers))) |
|
(w (car x)) |
|
(loop (cdr writers) (cdr x))) |
|
(error (conc "record '" rname " expecting " (length fields) " items, got " (length x0)) |
|
x0))))))))) |
|
|
|
((equal? type "map") |
|
(let ((writer (schema-writer |
|
(or (alist-ref 'values schema) |
|
(error "map type is missing values" schema))))) |
|
(lambda (x) |
|
(write-map x writer)))) |
|
((equal? type "array") (error "TODO: array type")) |
|
|
|
(else (schema-writer type))))) |
|
|
|
(else (error "unknown schema form" schema)))) |
|
|
|
(with-output-to-string |
|
(lambda () |
|
((schema-writer `((type . "record") |
|
(name . "user") |
|
(fields . #( ((name . "name") |
|
(type . "string")) |
|
|
|
((name . "age") |
|
(type . "long")) |
|
|
|
((name . "scores") |
|
(type . ((type . "map") |
|
(values . "string")))))))) |
|
`(user "king" 2 ((cake . "blah") (color . "red"))))))) |