<commit_before>(ns bytebuf.spec
  (:refer-clojure :exclude [type read float double long short byte])
  (:require [bytebuf.proto :as proto :refer [IStaticSize]]
            [bytebuf.buffer :as buffer]
            [bytebuf.bytes :as bytes]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Abstraction definition
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defprotocol ISpec
  "Basic abstraction for something that can be work like a Spec."
  (read [_ buff start] "Read all data from buffer.")
  (write [_ buff start data] "Read all data from buffer."))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Spec Constructors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defmulti spec
  "Polymorphic constructor for Spec instances.

  Spec is a some kind of composition of arbitrary
  number of types in associative or indexed data
  structure.

  Little example on how to create associative
  composition:

    (spec :field1 (long)
          :field2 (string 20))

  An other example on how to create indexed
  composition that represents the same bytes
  representation that previous one:

    (spec (long) (string 20))

  The main difference between the two reprensentation
  is that if you read a buffer using an associative
  spec, the result will be clojure hash-map, and if
  indexed spec is used, the result will be clojure
  vector containing the values.

  The same rules applies for writing data into a
  buffer."
  (fn [& params]
    (let [numparams (count params)]
      (cond
        (and (even? numparams)
             (keyword? (first params))
             (satisfies? ISpec (second params)))
        :associative

        (every? #(satisfies? ISpec %) params)
        :indexed))))

(defmethod spec :associative
  [& params]
  (let [data (mapv vec (partition 2 params))
        dict (into {} data)
        types (map second data)]
    (reify
      clojure.lang.Counted
      (count [_]
        (count types))

      IStaticSize
      (size [_]
        (reduce #(+ %1 (proto/size %2)) 0 types))

      ISpec
      (read [_ buff pos]
        (loop [index pos result {} pairs data]
          (if-let [[fieldname type] (first pairs)]
            (let [[readedbytes readeddata] (read type buff index)]
              (recur (+ index readedbytes)
                     (assoc result fieldname readeddata)
                     (rest pairs)))
            [(- index pos) result])))

      (write [_ buff pos data']
        (let [written (reduce (fn [index [fieldname type]]
                                (let [value (get data' fieldname nil)
                                      written (write type buff index value)]
                                  (+ index written)))
                              pos data)]
          (- written pos))))))

(defmethod spec :indexed
  [& types]
  (reify
    clojure.lang.Counted
    (count [_]
      (count types))

    IStaticSize
    (size [_]
      (reduce #(+ %1 (proto/size %2)) 0 types))

    ISpec
    (read [_ buff pos]
      (loop [index pos result [] types types]
        (if-let [type (first types)]
          (let [[readedbytes readeddata] (read type buff index)]
            (recur (+ index readedbytes)
                   (conj result readeddata)
                   (rest types)))
          [(- index pos) result])))

    (write [_ buff pos data']
      (let [indexedtypes (map-indexed vector types)
            written (reduce (fn [pos [index type]]
                              (let [value (nth data' index nil)
                                    written (write type buff pos value)]
                                (+ pos written)))
                            pos indexedtypes)]
        (- written pos)))))

(def ^{:doc "A semantic alias for spec constructor."}
  compose-type spec)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Types implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn bool
  "Boolean type spec constructor."
  ([] (bool nil))
  ([default]
   (reify
     IStaticSize
     (size [_] 1)

     ISpec
     (read [_ buff pos]
       (let [readed (buffer/read-byte buff pos)]
         [1 (condp = readed
              (clojure.core/byte 0) false
              (clojure.core/byte 1) true
              nil)]))

     (write [_ buff pos value]
       (let [value (condp = value
                     true (clojure.core/byte 1)
                     false (clojure.core/byte 0)
                     (clojure.core/byte -1))]
         (buffer/write-byte buff pos value)
         1)))))

(defn byte
  "Byte type spec constructor."
  ([] (byte (clojure.core/byte 0)))
  ([default]
   (reify
     IStaticSize
     (size [_] 1)

     ISpec
     (read [_ buff pos]
       (let [readed (buffer/read-byte buff pos)]
         [1 readed]))

     (write [_ buff pos value]
       (let [value (clojure.core/byte (or value default))]
         (buffer/write-byte buff pos value)
         1)))))

(defn int16
  "Short type spec constructor."
  ([] (int16 0))
  ([default]
   (reify
     IStaticSize
     (size [_] (Short/BYTES))

     ISpec
     (read [_ buff pos]
       [(Short/BYTES)
        (buffer/read-short buff pos)])

     (write [_ buff pos value]
       (let [value (or value default)]
         (buffer/write-short buff pos value)
         (Short/BYTES))))))


(defn int32
  "Integer type spec constructor."
  ([] (int32 0))
  ([default]
   (reify
     IStaticSize
     (size [_] (Integer/BYTES))

     ISpec
     (read [_ buff pos]
       [(Integer/BYTES)
        (buffer/read-int buff pos)])

     (write [_ buff pos value]
       (let [value (or value default)]
         (buffer/write-int buff pos value)<commit_msg>Define composed specs as named type (with deftype).<commit_after>(ns bytebuf.spec
  (:refer-clojure :exclude [type read float double long short byte])
  (:require [bytebuf.proto :as proto :refer [IStaticSize]]
            [bytebuf.buffer :as buffer]
            [bytebuf.bytes :as bytes]))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Abstraction definition
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defprotocol ISpec
  "Basic abstraction for something that can be work like a Spec."
  (read [_ buff start] "Read all data from buffer.")
  (write [_ buff start data] "Read all data from buffer."))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Composed Spec Types
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(deftype AssociativeSpec [data dict types]
  clojure.lang.Counted
  (count [_]
    (count types))

  IStaticSize
  (size [_]
    (reduce #(+ %1 (proto/size %2)) 0 types))

  ISpec
  (read [_ buff pos]
    (loop [index pos result {} pairs data]
      (if-let [[fieldname type] (first pairs)]
        (let [[readedbytes readeddata] (read type buff index)]
          (recur (+ index readedbytes)
                 (assoc result fieldname readeddata)
                 (rest pairs)))
        [(- index pos) result])))

  (write [_ buff pos data']
    (let [written (reduce (fn [index [fieldname type]]
                            (let [value (get data' fieldname nil)
                                  written (write type buff index value)]
                              (+ index written)))
                          pos data)]
      (- written pos))))

(deftype IndexedSpec [types]
  clojure.lang.Counted
  (count [_]
    (count types))

  IStaticSize
  (size [_]
    (reduce #(+ %1 (proto/size %2)) 0 types))

  ISpec
  (read [_ buff pos]
    (loop [index pos result [] types types]
      (if-let [type (first types)]
        (let [[readedbytes readeddata] (read type buff index)]
          (recur (+ index readedbytes)
                 (conj result readeddata)
                 (rest types)))
        [(- index pos) result])))

  (write [_ buff pos data']
    (let [indexedtypes (map-indexed vector types)
          written (reduce (fn [pos [index type]]
                            (let [value (nth data' index nil)
                                  written (write type buff pos value)]
                              (+ pos written)))
                          pos indexedtypes)]
      (- written pos))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Spec Constructors
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defmulti spec
  "Polymorphic constructor for Spec instances.

  Spec is a some kind of composition of arbitrary
  number of types in associative or indexed data
  structure.

  Little example on how to create associative
  composition:

    (spec :field1 (long)
          :field2 (string 20))

  An other example on how to create indexed
  composition that represents the same bytes
  representation that previous one:

    (spec (long) (string 20))

  The main difference between the two reprensentation
  is that if you read a buffer using an associative
  spec, the result will be clojure hash-map, and if
  indexed spec is used, the result will be clojure
  vector containing the values.

  The same rules applies for writing data into a
  buffer."
  (fn [& params]
    (let [numparams (count params)]
      (cond
        (every? #(satisfies? ISpec %) params)
        :indexed

        (and (even? numparams)
             (keyword? (first params))
             (satisfies? ISpec (second params)))
        :associative))))

(defmethod spec :associative
  [& params]
  (let [data (mapv vec (partition 2 params))
        dict (into {} data)
        types (map second data)]
    (AssociativeSpec. data dict types)))

(defmethod spec :indexed
  [& types]
  (IndexedSpec. types))

(def ^{:doc "A semantic alias for spec constructor."}
  compose-type spec)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Types implementation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defn bool
  "Boolean type spec constructor."
  ([] (bool nil))
  ([default]
   (reify
     IStaticSize
     (size [_] 1)

     ISpec
     (read [_ buff pos]
       (let [readed (buffer/read-byte buff pos)]
         [1 (condp = readed
              (clojure.core/byte 0) false
              (clojure.core/byte 1) true
              nil)]))

     (write [_ buff pos value]
       (let [value (condp = value
                     true (clojure.core/byte 1)
                     false (clojure.core/byte 0)
                     (clojure.core/byte -1))]
         (buffer/write-byte buff pos value)
         1)))))

(defn byte
  "Byte type spec constructor."
  ([] (byte (clojure.core/byte 0)))
  ([default]
   (reify
     IStaticSize
     (size [_] 1)

     ISpec
     (read [_ buff pos]
       (let [readed (buffer/read-byte buff pos)]
         [1 readed]))

     (write [_ buff pos value]
       (let [value (clojure.core/byte (or value default))]
         (buffer/write-byte buff pos value)
         1)))))

(defn int16
  "Short type spec constructor."
  ([] (int16 0))
  ([default]
   (reify
     IStaticSize
     (size [_] (Short/BYTES))

     ISpec
     (read [_ buff pos]
       [(Short/BYTES)
        (buffer/read-short buff pos)])

     (write [_ buff pos value]
       (let [value (or value default)]
         (buffer/write-short buff pos value)
         (Short/BYTES))))))

(defn int32
  "Integer type spec constructor."
  ([] (int32 0))
  ([default]
   (reify
     IStaticSize
     (size [_] (Integer/BYTES))

     ISpec
     (read [_ buff pos]
       [(Integer/BYTES)
        (buffer/read-int buff pos)])

     (write [_ buff pos value]
       (let [value (or value default)]
         (buffer/write-int buff pos value)
{'fraction_non_alphanumeric': 0.20240984018073802, 'fraction_numerical': 0.005857250439293783, 'mean_word_length': 2.308054248546914, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1162620', 'n_tokens_mistral': 3915, 'n_tokens_neox': 3406, 'n_words': 1101}