octal.scm 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. ;;; Disarchive
  2. ;;; Copyright © 2020, 2021 Timothy Sample <samplet@ngyro.com>
  3. ;;;
  4. ;;; This file is part of Disarchive.
  5. ;;;
  6. ;;; Disarchive is free software: you can redistribute it and/or modify
  7. ;;; it under the terms of the GNU General Public License as published by
  8. ;;; the Free Software Foundation, either version 3 of the License, or
  9. ;;; (at your option) any later version.
  10. ;;;
  11. ;;; Disarchive is distributed in the hope that it will be useful,
  12. ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. ;;; GNU General Public License for more details.
  15. ;;;
  16. ;;; You should have received a copy of the GNU General Public License
  17. ;;; along with Disarchive. If not, see <http://www.gnu.org/licenses/>.
  18. (define-module (disarchive kinds octal)
  19. #:use-module (disarchive kinds binary-string)
  20. #:use-module (disarchive kinds zero-string)
  21. #:use-module (disarchive serialization)
  22. #:use-module (disarchive utils)
  23. #:use-module (ice-9 match)
  24. #:use-module (rnrs bytevectors)
  25. #:use-module (srfi srfi-9 gnu)
  26. #:use-module (srfi srfi-71)
  27. #:export (<padded-octal>
  28. make-padded-octal
  29. padded-octal?
  30. padded-octal-value
  31. padded-octal-width
  32. padded-octal-padding
  33. padded-octal-trailer
  34. <unstructured-octal>
  35. make-unstructured-octal
  36. unstructured-octal?
  37. unstructured-octal-value
  38. unstructured-octal-source
  39. octal?
  40. valid-octal?
  41. octal-value
  42. set-octal-value
  43. decode-octal
  44. encode-octal
  45. -octal-))
  46. ;;; Commentary:
  47. ;;;
  48. ;;; A formatted octal value represents a number that comes from a
  49. ;;; sequence of octal digits with a specific format. For example,
  50. ;;; "00010" would have the value 8 with width 5 and padding "0".
  51. ;;;
  52. ;;; Code:
  53. (define-immutable-record-type <padded-octal>
  54. (make-padded-octal value width padding trailer)
  55. padded-octal?
  56. (value padded-octal-value set-padded-octal-value)
  57. (width padded-octal-width)
  58. (padding padded-octal-padding)
  59. (trailer padded-octal-trailer))
  60. (define (natural? n)
  61. (and (exact? n) (integer? n) (not (negative? n))))
  62. (define (valid-padded-octal? octal)
  63. ;; We check three properties here. First, the padding character
  64. ;; must not be a nonzero octal digit. Second, the width must be
  65. ;; large enough to accomodate the value. Third, if the padding
  66. ;; character is not used (because the size of the value is the same
  67. ;; as the width) it must be '#\0'.
  68. (and (match octal
  69. (($ <padded-octal>
  70. (? natural?)
  71. (? natural?)
  72. (and (? char?)
  73. (? (lambda (x)
  74. (not (char-set-contains? char-set:octal-nonzero x)))))
  75. (? valid-binary-string?))
  76. #t)
  77. (_ #f))
  78. (let* ((value (padded-octal-value octal))
  79. (width (padded-octal-width octal))
  80. (padding (padded-octal-padding octal))
  81. (size (string-length (number->string value 8))))
  82. (and (<= size width)
  83. (or (char=? padding #\0)
  84. (< size width))))))
  85. (define-immutable-record-type <unstructured-octal>
  86. (make-unstructured-octal value source)
  87. unstructured-octal?
  88. (value unstructured-octal-value set-unstructured-octal-value)
  89. (source unstructured-octal-source))
  90. (define (extract-octal str)
  91. (match (string-index str char-set:octal)
  92. (#f #f)
  93. (start (let ((end (or (string-index str char-set:non-octal start)
  94. (string-length str))))
  95. (string->number (substring str start end) 8)))))
  96. (define (valid-unstructured-octal? octal)
  97. (and (match octal
  98. (($ <unstructured-octal>
  99. (? natural?)
  100. (? valid-zero-string?))
  101. #t)
  102. (_ #f))
  103. ;; Check that we are dealing with an unstructured octal and not
  104. ;; something that would be better represented as a padded octal.
  105. (let* ((zstr (unstructured-octal-source octal))
  106. (str (zero-string-value zstr))
  107. (trailer (zero-string-trailer zstr)))
  108. (or (not (string? str))
  109. (not (string->padded-octal str trailer))))
  110. ;; Check that the value corresponds to the source.
  111. (match (zero-string-value (unstructured-octal-source octal))
  112. ((? string? str) (= (or (extract-octal str) 0)
  113. (unstructured-octal-value octal)))
  114. (_ (zero? (unstructured-octal-value octal))))))
  115. (define (octal? obj)
  116. "Check if OBJ is a formatted octal value."
  117. (match obj
  118. ((? padded-octal?) #t)
  119. ((? unstructured-octal?) #t)
  120. (_ #f)))
  121. (define (valid-octal? octal)
  122. "Check that OCTAL satisfies the constraints of a formatted octal
  123. value."
  124. (or (valid-padded-octal? octal)
  125. (valid-unstructured-octal? octal)))
  126. (define (octal-value octal)
  127. (match octal
  128. ((? padded-octal?) (padded-octal-value octal))
  129. ((? unstructured-octal?) (unstructured-octal-value octal))
  130. (_ (scm-error 'wrong-type-arg 'octal-value
  131. (string-append "Wrong type argument in position 1 "
  132. "(expecting octal): ~A")
  133. (list octal) (list octal)))))
  134. (define (set-octal-value octal value)
  135. (match octal
  136. ((? padded-octal?) (set-padded-octal-value octal value))
  137. ((? unstructured-octal?) (set-unstructured-octal-value octal value))
  138. (_ (scm-error 'wrong-type-arg 'set-octal-value
  139. (string-append "Wrong type argument in position 1 "
  140. "(expecting octal): ~A")
  141. (list octal) (list octal)))))
  142. (define (string-first str)
  143. "Get the first character in STR."
  144. (string-ref str 0))
  145. (define char-set:octal (string->char-set "01234567"))
  146. (define char-set:octal-nonzero (string->char-set "1234567"))
  147. (define char-set:non-octal (char-set-complement char-set:octal))
  148. (define* (string->padded-octal str #:optional (trailer ""))
  149. (define width (string-length str))
  150. (match (string-index str char-set:octal-nonzero)
  151. (#f (and (not (string-null? str))
  152. (char=? (string-ref str (1- (string-length str))) #\0)
  153. (string-every (string-first str) str 0 (1- (string-length str)))
  154. (make-padded-octal 0 width (string-first str) trailer)))
  155. (start (cond
  156. ((string-index str char-set:non-octal start) #f)
  157. ((zero? start) (make-padded-octal (string->number str 8)
  158. width #\0 trailer))
  159. ((string-every (string-first str) str 0 start)
  160. (make-padded-octal (string->number (substring str start) 8)
  161. width (string-first str) trailer))
  162. (else #f)))))
  163. (define (string->unstructured-octal str)
  164. (match (string-index str char-set:octal)
  165. (#f (make-unstructured-octal 0 str))))
  166. (define (zero-string->octal zstr)
  167. "Convert the zero string ZSTR into an octal value."
  168. (match zstr
  169. (($ <zero-string> (? string? str) trailer)
  170. (or (string->padded-octal str trailer)
  171. (make-unstructured-octal (or (extract-octal str) 0) zstr)))
  172. (($ <zero-string> (? bytevector? bv) trailer)
  173. (make-unstructured-octal 0 zstr))))
  174. (define* (decode-octal bv #:optional (start 0)
  175. (end (bytevector-length bv)))
  176. "Decode the contents of the bytevector BV as a formatted octal value.
  177. Optionally, START and END indexes can be provided to decode only a
  178. part of BV."
  179. (zero-string->octal (decode-zero-string bv start end)))
  180. (define (padded-octal->zero-string octal)
  181. (match-let* ((($ <padded-octal> value width padding trailer) octal)
  182. (str (number->string value 8))
  183. (size (max 0 (- width (string-length str))))
  184. (padding-str (make-string size padding)))
  185. (make-zero-string (string-append padding-str str) trailer)))
  186. (define* (encode-octal octal #:optional bv (start 0) end)
  187. "Encode the octal value OCTAL. If BV is set, the result will be
  188. written into BV. Otherwise, the result will be written into a new
  189. bytevector. If you are providing a bytevector, you can also provide
  190. START and END indexes to control where the result is written."
  191. (let ((zstr (match octal
  192. ((? padded-octal?) (padded-octal->zero-string octal))
  193. ((? unstructured-octal?) (unstructured-octal-source octal)))))
  194. (encode-zero-string zstr bv start end)))
  195. (define -padded-octal-
  196. (make-record-serializer
  197. make-padded-octal
  198. `((value ,padded-octal-value #f)
  199. (width ,padded-octal-width #f)
  200. (padding ,padded-octal-padding #f)
  201. (trailer ,padded-octal-trailer ,-binary-string-))
  202. #:elide-first-field? #t))
  203. (define -unstructured-octal-
  204. (make-record-serializer
  205. make-unstructured-octal
  206. `((value ,unstructured-octal-value #f)
  207. (source ,unstructured-octal-source ,-zero-string-))
  208. #:elide-first-field? #t))
  209. (define* (octal->sexp octal #:optional defaults)
  210. (match octal
  211. ((? padded-octal?)
  212. (serialize -padded-octal- octal
  213. (and (padded-octal? defaults) defaults)))
  214. ((? unstructured-octal?)
  215. (serialize -unstructured-octal- octal #f))
  216. (_ (scm-error 'wrong-type-arg 'octal->sexp
  217. (string-append "Wrong type argument in position 1 "
  218. "(expecting octal): ~A")
  219. (list octal) (list octal)))))
  220. (define* (sexp->octal obj #:optional defaults)
  221. (if (assoc-ref obj 'source)
  222. (deserialize -unstructured-octal- obj #f)
  223. (deserialize -padded-octal- obj
  224. (and (padded-octal? defaults) defaults))))
  225. (define -octal- (make-serializer octal->sexp sexp->octal))