| 1 | (defpackage #:binary-class |
|---|
| 2 | (:use #:cl #:cl-ppcre) |
|---|
| 3 | (:export #:lisp-type-for #:default-for #:read-binary #:define-file-struct)) |
|---|
| 4 | |
|---|
| 5 | (in-package #:binary-class) |
|---|
| 6 | |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | (defstruct member-def |
|---|
| 10 | name |
|---|
| 11 | type |
|---|
| 12 | format) |
|---|
| 13 | |
|---|
| 14 | (defun parse-member-def (def) |
|---|
| 15 | (destructuring-bind (name type format) def |
|---|
| 16 | (make-member-def :name name :type type :format format))) |
|---|
| 17 | |
|---|
| 18 | (defgeneric lisp-type-for (binary-type) |
|---|
| 19 | (:method ((type (eql :unsigned))) |
|---|
| 20 | '(integer 0)) |
|---|
| 21 | (:method ((type (eql :string))) |
|---|
| 22 | 'string) |
|---|
| 23 | (:method ((type (eql :date))) |
|---|
| 24 | 't)) |
|---|
| 25 | |
|---|
| 26 | (defgeneric default-for (binary-type) |
|---|
| 27 | (:method ((type (eql :unsigned))) |
|---|
| 28 | 0) |
|---|
| 29 | (:method ((type (eql :string))) |
|---|
| 30 | "") |
|---|
| 31 | (:method ((type (eql :date))) |
|---|
| 32 | (encode-universal-time 0 0 0 01 01 1970))) |
|---|
| 33 | |
|---|
| 34 | (defgeneric read-binary (type stream &optional format) |
|---|
| 35 | (:method ((type (eql :unsigned)) stream &optional (format 1)) |
|---|
| 36 | (let ((r 0)) |
|---|
| 37 | (dotimes (i format r) |
|---|
| 38 | (setf (ldb (byte 8 (* i 8)) r) (read-byte stream))))) |
|---|
| 39 | (:method ((type (eql :string)) stream &optional format) |
|---|
| 40 | (let ((v (make-sequence '(vector (unsigned-byte 8)) format |
|---|
| 41 | :initial-element 0))) |
|---|
| 42 | (read-sequence v stream) |
|---|
| 43 | (string-trim '(#\Nul #\Space) (map 'string #'code-char v)))) |
|---|
| 44 | (:method ((type (eql :date)) stream &optional format) |
|---|
| 45 | (assert (stringp format)) |
|---|
| 46 | (cond |
|---|
| 47 | ((string= format "YYMMDD") |
|---|
| 48 | (let* ((year (read-binary :unsigned stream)) |
|---|
| 49 | (month (read-binary :unsigned stream)) |
|---|
| 50 | (day (read-binary :unsigned stream))) |
|---|
| 51 | (encode-universal-time 0 0 0 day month year))) |
|---|
| 52 | ((string= format "YYYYMMDD") |
|---|
| 53 | (let* ((year (read-binary :unsigned stream 2)) |
|---|
| 54 | (month (read-binary :unsigned stream)) |
|---|
| 55 | (day (read-binary :unsigned stream))) |
|---|
| 56 | (encode-universal-time 0 0 0 day month year))) |
|---|
| 57 | (t |
|---|
| 58 | (error "Date-format must be either \"YYMMDD\" or \"YYYYMMDD\"."))))) |
|---|
| 59 | |
|---|
| 60 | (defmacro define-file-struct (name &body defs) |
|---|
| 61 | (let ((defs (mapcar #'parse-member-def defs))) |
|---|
| 62 | `(progn |
|---|
| 63 | (defstruct ,name |
|---|
| 64 | ,@(mapcar (lambda (def) |
|---|
| 65 | (with-slots (name type) def |
|---|
| 66 | `(,name ,(default-for type) |
|---|
| 67 | :type ,(lisp-type-for type)))) |
|---|
| 68 | (remove-if #'null defs :key #'member-def-type))) |
|---|
| 69 | (defmethod read-binary ((type (eql ',name)) stream &optional format) |
|---|
| 70 | (declare (ignore format)) |
|---|
| 71 | (let ((rec (make-instance ',name))) |
|---|
| 72 | ,@(mapcar (lambda (def) |
|---|
| 73 | (with-slots (name type format) def |
|---|
| 74 | (if name |
|---|
| 75 | `(setf (slot-value rec ',name) |
|---|
| 76 | (read-binary ',type stream ,format)) |
|---|
| 77 | `(read-binary :unsigned stream ,format)))) |
|---|
| 78 | defs) |
|---|
| 79 | rec))))) |
|---|