git.fiddlerwoaroof.com
Raw Blame History
(uiop:define-package :fwoar.bin-parser
    (:use :cl)
  (:mix :fw.lu :alexandria :serapeum)
  (:export :le->int
           :read-bytes
           :extract
           :extract-let))

(in-package :fwoar.bin-parser)

(defun read-bytes (n s)
  (with (seq (make-array n :element-type 'octet))
    (values seq
            (read-sequence seq s))))

(defun calculate-sizes (desc)
  (reduce #'+ desc
          :key #'cadr
          :initial-value 0))

(defun le->int (bytes)
  (cadr
   (reduce (op (destructuring-bind (count val) _1
                 (list (1+ count)
                       (+ val
                          (ash _2
                               (* count 8))))))
           bytes
           :initial-value (list 0 0))))

(defun get-extractable-bytes (desc &optional (bindings ()))
  (loop for ((name size . other) . rest) on (resolve-sizes desc bindings)
     until (symbolp size)
     collect (list* name size other) into extractable
     finally (return (values extractable
                             (append (unsplice
                                      (when (symbolp size)
                                        (list* name size other)))
                                     rest))))) 

(defun resolve-sizes (desc extant-bindings)
  (declare (optimize (debug 3)))
  (loop with bindings = (copy-seq extant-bindings)
     for (name size . rest) in desc
     for resolved = (when (symbolp size)
                      (cdr (assoc size bindings)))
     when resolved do (push (cons name resolved)
                            bindings)
     if resolved collect (list* name resolved rest) into new-desc
     else collect (list* name size rest) into new-desc
     finally (return (values new-desc
                             (remove-duplicates (append (mapcar (op (apply #'cons (subseq _ 0 2)))
                                                                new-desc)
                                                        bindings)
                                                :key 'car
                                                :from-end t)))))

(defun extract-bytes (desc bytes)
  (loop
     with cur-idx = 0
     for (name size . rest) in desc
     for next-seq = (subseq bytes cur-idx
                            (+ cur-idx size))
     collect (cons name (if rest
                            (funcall (car rest) next-seq)
                            next-seq))
     do (incf cur-idx size)))

(defun parse-struct (desc s)
  (let* ((struct-size (calculate-sizes desc))
         (bytes (read-bytes struct-size s)))
    (extract-bytes desc bytes)))

(defun make-zipfile-stream (fn)
  (open fn :element-type '(unsigned-byte 8)))


(defun extract (raw-desc s &optional bindings)
  (multiple-value-bind (desc remainder) (get-extractable-bytes raw-desc bindings)
    (let ((next-segment (parse-struct desc s)))
      (if remainder
          (append next-segment
                  (extract remainder s (append next-segment bindings)))
          next-segment))))

(defmacro extract-let ((&rest bindings) parser s &body body)
  (labels ((collect-binding (binding-spec)
             (destructuring-bind (name target) binding-spec
               `(,name (cdr (assoc ,target it)))))
           (collect-bindings ()
             (mapcar #'collect-binding bindings)))
    `(let* ((it (extract ,parser ,s))
            ,@(collect-bindings))
       ,@body)))