soft-ports.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. ;;; "Soft" ports
  2. ;;; Copyright (C) 2023 Free Software Foundation, Inc.
  3. ;;;
  4. ;;; This library is free software: you can redistribute it and/or modify
  5. ;;; it under the terms of the GNU Lesser General Public License as
  6. ;;; published by the Free Software Foundation, either version 3 of the
  7. ;;; License, or (at your option) any later version.
  8. ;;;
  9. ;;; This library is distributed in the hope that it will be useful, but
  10. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;;; Lesser General Public License for more details.
  13. ;;;
  14. ;;; You should have received a copy of the GNU Lesser General Public
  15. ;;; License along with this program. If not, see
  16. ;;; <http://www.gnu.org/licenses/>.
  17. ;;; Commentary:
  18. ;;;
  19. ;;; Implementation of legacy soft-port interface.
  20. ;;;
  21. ;;; Code:
  22. (define-module (ice-9 soft-ports)
  23. #:use-module (ice-9 binary-ports)
  24. #:use-module (ice-9 custom-ports)
  25. #:use-module (ice-9 textual-ports)
  26. #:use-module (ice-9 match)
  27. #:use-module (rnrs bytevectors)
  28. #:use-module (rnrs bytevectors gnu)
  29. #:export (deprecated-make-soft-port)
  30. #:replace (make-soft-port))
  31. (define (type-error proc expecting val)
  32. (scm-error 'wrong-type-arg proc "Wrong type (expecting `~S'): ~S"
  33. (list expecting val) (list val)))
  34. (define (deprecated-soft-port-read %get-char)
  35. (unless (procedure? %get-char)
  36. (type-error "deprecated-soft-port-read" "procedure" %get-char))
  37. (define encode-buf-size 10)
  38. (define buffer (make-bytevector encode-buf-size))
  39. (define buffer-pos 0)
  40. (define buffer-len 0)
  41. (define transcoder
  42. (make-custom-binary-output-port
  43. "transcoder"
  44. (lambda (bv start count)
  45. (let ((to-copy (min encode-buf-size count)))
  46. (bytevector-copy! bv start buffer 0 to-copy)
  47. (set! buffer-pos 0)
  48. (set! buffer-len to-copy)
  49. to-copy))
  50. #f #f #f))
  51. (lambda (port bv start count)
  52. (let lp ((start start) (count count) (ret 0))
  53. (unless (< buffer-pos buffer-len)
  54. (match (%get-char)
  55. ((or #f (? eof-object?)) ret)
  56. (ch
  57. (unless (eq? (port-encoding port) (port-encoding transcoder))
  58. (set-port-encoding! transcoder (port-encoding port)))
  59. (unless (eq? (port-conversion-strategy port)
  60. (port-conversion-strategy transcoder))
  61. (set-port-conversion-strategy! transcoder
  62. (port-conversion-strategy port)))
  63. (put-char transcoder ch)
  64. (force-output transcoder))))
  65. (let ((to-copy (min count (- buffer-len buffer-pos))))
  66. (bytevector-copy! buffer buffer-pos bv start to-copy)
  67. (set! buffer-pos (+ buffer-pos to-copy))
  68. to-copy))))
  69. (define (deprecated-soft-port-write %put-string %flush)
  70. (unless (procedure? %put-string)
  71. (type-error "deprecated-soft-port-write" "procedure" %put-string))
  72. (when %flush
  73. (unless (procedure? %flush)
  74. (type-error "deprecated-soft-port-write" "procedure" %flush)))
  75. (lambda (port bv start count)
  76. (let* ((bytes (bytevector-slice bv start count))
  77. (str (call-with-input-bytevector
  78. bytes
  79. (lambda (bport)
  80. (set-port-encoding! bport (port-encoding port))
  81. (set-port-conversion-strategy!
  82. bport
  83. (port-conversion-strategy port))
  84. (get-string-all bport)))))
  85. (%put-string str)
  86. (if %flush (%flush))
  87. count)))
  88. (define (deprecated-soft-port-close %close)
  89. (unless (procedure? %close)
  90. (type-error "soft-port-close" "procedure" %close))
  91. (lambda (port) (%close)))
  92. (define (deprecated-soft-port-input-waiting? %input-ready)
  93. (unless (procedure? %input-ready)
  94. (type-error "deprecated-soft-port-close" "procedure" %input-ready))
  95. (lambda (port) (< 0 (%input-ready))))
  96. (define (%deprecated-make-soft-port %put-char %put-string %flush %get-char
  97. %close %input-ready
  98. reading? writing? buffering)
  99. (cond
  100. ((not (or reading? writing?))
  101. (%make-void-port ""))
  102. (else
  103. (let ((port
  104. (make-custom-port
  105. #:id "soft-port"
  106. #:read (and reading? (deprecated-soft-port-read %get-char))
  107. #:write (and writing? (deprecated-soft-port-write %put-string %flush))
  108. #:seek (lambda (port offset whence)
  109. (error "soft ports are not seekable"))
  110. #:close (and %close
  111. (deprecated-soft-port-close %close))
  112. #:get-natural-buffer-sizes (lambda (port read-size write-size)
  113. ;; The in-practice expectation
  114. ;; is that soft ports have
  115. ;; unbuffered output.
  116. (values read-size 1))
  117. #:random-access? (lambda (port) #f)
  118. #:input-waiting? (if %input-ready
  119. (deprecated-soft-port-input-waiting? %input-ready)
  120. (lambda (port) #t))
  121. #:close-on-gc? #t)))
  122. (when buffering
  123. (setvbuf port buffering))
  124. port))))
  125. (define (deprecated-make-soft-port vtable modes)
  126. "Return a port capable of receiving or delivering characters as
  127. specified by the @var{modes} string (@pxref{File Ports, open-file}).
  128. @var{pv} must be a vector of length 5 or 6. Its components are as
  129. follows:
  130. @enumerate 0
  131. @item
  132. procedure accepting one character for output
  133. @item
  134. procedure accepting a string for output
  135. @item
  136. thunk for flushing output
  137. @item
  138. thunk for getting one character
  139. @item
  140. thunk for closing port (not by garbage collection)
  141. @item
  142. (if present and not @code{#f}) thunk for computing the number of
  143. characters that can be read from the port without blocking. @end
  144. enumerate
  145. For an output-only port only elements 0, 1, 2, and 4 need be procedures.
  146. For an input-only port only elements 3 and 4 need be procedures. Thunks
  147. 2 and 4 can instead be @code{#f} if there is no useful operation for
  148. them to perform.
  149. If thunk 3 returns @code{#f} or an @code{eof-object}
  150. (@pxref{Input, eof-object?, ,r5rs, The Revised^5 Report on
  151. Scheme}) it indicates that the port has reached end-of-file.
  152. For example:
  153. @lisp
  154. (define stdout (current-output-port))
  155. (define p (make-soft-port
  156. (vector
  157. (lambda (c) (write c stdout))
  158. (lambda (s) (display s stdout))
  159. (lambda () (display \".\" stdout))
  160. (lambda () (char-upcase (read-char)))
  161. (lambda () (display \"@@\" stdout)))
  162. \"rw\"))
  163. (write p p) @result{} #<input-output: soft 8081e20>
  164. @end lisp"
  165. (define reading?
  166. (or (string-index modes #\r)
  167. (string-index modes #\+)))
  168. (define writing?
  169. (or (string-index modes #\w)
  170. (string-index modes #\a)
  171. (string-index modes #\+)))
  172. (define buffering
  173. (and writing?
  174. (cond
  175. ((string-index modes #\0) 'none)
  176. ((string-index modes #\l) 'line)
  177. (else #f))))
  178. (match vtable
  179. (#(%put-char %put-string %flush %get-char %close)
  180. (%deprecated-make-soft-port %put-char %put-string %flush %get-char %close
  181. #f reading? writing? buffering))
  182. (#(%put-char %put-string %flush %get-char %close %chars-waiting)
  183. (%deprecated-make-soft-port %put-char %put-string %flush %get-char %close
  184. %chars-waiting reading? writing? buffering))))
  185. (define (soft-port-read read-string)
  186. (unless (procedure? read-string)
  187. (type-error "soft-port-read" "procedure" read-string))
  188. (define-values (transcoder get-bytes) (open-bytevector-output-port))
  189. (define buffer #f)
  190. (define buffer-pos 0)
  191. (lambda (port bv start count)
  192. (unless (and buffer (< buffer-pos (bytevector-length buffer)))
  193. (let* ((str (read-string)))
  194. (unless (eq? (port-encoding port) (port-encoding transcoder))
  195. (set-port-encoding! transcoder (port-encoding port)))
  196. (unless (eq? (port-conversion-strategy port)
  197. (port-conversion-strategy transcoder))
  198. (set-port-conversion-strategy! transcoder
  199. (port-conversion-strategy port)))
  200. (put-string transcoder str)
  201. (set! buffer (get-bytes))
  202. (set! buffer-pos 0)))
  203. (let ((to-copy (min count (- (bytevector-length buffer) buffer-pos))))
  204. (bytevector-copy! buffer buffer-pos bv start to-copy)
  205. (if (= (bytevector-length buffer) (+ buffer-pos to-copy))
  206. (set! buffer #f)
  207. (set! buffer-pos (+ buffer-pos to-copy)))
  208. to-copy)))
  209. (define (soft-port-write write-string)
  210. (unless (procedure? write-string)
  211. (type-error "soft-port-write" "procedure" write-string))
  212. (lambda (port bv start count)
  213. (write-string
  214. (call-with-input-bytevector
  215. (bytevector-slice bv start count)
  216. (lambda (bport)
  217. (set-port-encoding! bport (port-encoding port))
  218. (set-port-conversion-strategy!
  219. bport
  220. (port-conversion-strategy port))
  221. (get-string-all bport))))
  222. count))
  223. (define* (make-soft-port #:key
  224. (id "soft-port")
  225. (read-string #f)
  226. (write-string #f)
  227. (input-waiting? #f)
  228. (close #f)
  229. (close-on-gc? #f))
  230. "Return a new port. If the @var{read-string} keyword argument is
  231. present, the port will be an input port. If @var{write-string} is
  232. present, the port will be an output port. If both are supplied, the
  233. port will be open for input and output.
  234. When the port's internal buffers are empty, @var{read-string} will be
  235. called with no arguments, and should return a string. Returning \"\"
  236. indicates end-of-stream. Similarly when a port flushes its write
  237. buffer, the characters in that buffer will be passed to the
  238. @var{write-string} procedure as its single argument. @var{write-string}
  239. returns unspecified values.
  240. If supplied, @var{input-waiting?} should return @code{#t} if the soft
  241. port has input which would be returned directly by @var{read-string}.
  242. If supplied, @var{close} will be called when the port is closed, with no
  243. arguments. If @var{close-on-gc?} is @code{#t}, @var{close} will
  244. additionally be called when the port becomes unreachable, after flushing
  245. any pending write buffers."
  246. (unless (or read-string write-string)
  247. (error "Expected at least one of #:read-string, #:write-string"))
  248. (when (and input-waiting? (not read-string))
  249. (error "Supplying #:input-waiting? requires a #:read-string"))
  250. (make-custom-port
  251. #:id id
  252. #:read (and read-string (soft-port-read read-string))
  253. #:write (and write-string (soft-port-write write-string))
  254. #:close (and close (lambda (port) (close)))
  255. #:input-waiting? (and input-waiting?
  256. (lambda (port) (input-waiting?)))
  257. #:close-on-gc? close-on-gc?
  258. #:encoding 'UTF-8))