textual-ports.scm 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. ;;;; textual-ports.scm --- Textual I/O on ports
  2. ;;;; Copyright (C) 2016, 2023 Free Software Foundation, Inc.
  3. ;;;;
  4. ;;;; This library is free software; you can redistribute it and/or
  5. ;;;; modify it under the terms of the GNU Lesser General Public
  6. ;;;; License as published by the Free Software Foundation; either
  7. ;;;; version 3 of the License, or (at your option) any later version.
  8. ;;;;
  9. ;;;; This library is distributed in the hope that it will be useful,
  10. ;;;; but 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 library; if not, write to the Free Software
  16. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. ;;; Commentary:
  18. ;;;
  19. ;;; Code:
  20. (define-module (ice-9 textual-ports)
  21. #:use-module (ice-9 ports internal)
  22. #:use-module (ice-9 binary-ports)
  23. #:use-module (ice-9 custom-ports)
  24. #:use-module (ice-9 match)
  25. #:use-module (ice-9 rdelim)
  26. #:use-module (rnrs bytevectors)
  27. #:use-module (rnrs bytevectors gnu)
  28. #:re-export (get-string-n!
  29. put-char
  30. put-string)
  31. #:export (get-char
  32. unget-char
  33. unget-string
  34. lookahead-char
  35. get-string-n
  36. get-string-all
  37. get-line
  38. make-custom-textual-input-port
  39. make-custom-textual-output-port
  40. make-custom-textual-input/output-port))
  41. (define (get-char port)
  42. (read-char port))
  43. (define (lookahead-char port)
  44. (peek-char port))
  45. (define (unget-char port char)
  46. (unread-char char port))
  47. (define* (unget-string port string #:optional (start 0)
  48. (count (- (string-length string) start)))
  49. (unread-string (if (and (zero? start)
  50. (= count (string-length string)))
  51. string
  52. (substring/shared string start (+ start count)))
  53. port))
  54. (define (get-line port)
  55. (read-line port 'trim))
  56. (define (get-string-all port)
  57. (read-string port))
  58. (define (get-string-n port count)
  59. "Read up to @var{count} characters from @var{port}.
  60. If no characters could be read before encountering the end of file,
  61. return the end-of-file object, otherwise return a string containing
  62. the characters read."
  63. (let* ((s (make-string count))
  64. (rv (get-string-n! port s 0 count)))
  65. (cond ((eof-object? rv) rv)
  66. ((= rv count) s)
  67. (else (substring/shared s 0 rv)))))
  68. (define (type-error proc expecting val)
  69. (scm-error 'wrong-type-arg proc "Wrong type (expecting `~S'): ~S"
  70. (list expecting val) (list val)))
  71. (define (custom-textual-port-read+flush-input read)
  72. (unless (procedure? read)
  73. (type-error "custom-textual-port-read" "procedure" read))
  74. (define-values (transcoder get-bytes) (open-bytevector-output-port))
  75. (define buffer #f)
  76. (define buffer-pos 0)
  77. (define (%read port bv start count)
  78. (unless (and buffer (< buffer-pos (bytevector-length buffer)))
  79. (let* ((str (make-string (max (port-read-buffering port) 1)))
  80. (chars (read str 0 (string-length str))))
  81. (unless (and (exact-integer? chars) (<= 0 chars (string-length str)))
  82. (scm-error 'out-of-range "custom-textual-port-read"
  83. "Value out of range: ~S" (list chars) (list chars)))
  84. (unless (eq? (port-encoding port) (port-encoding transcoder))
  85. (set-port-encoding! transcoder (port-encoding port)))
  86. (unless (eq? (port-conversion-strategy port)
  87. (port-conversion-strategy transcoder))
  88. (set-port-conversion-strategy! transcoder
  89. (port-conversion-strategy port)))
  90. (put-string transcoder str 0 chars)
  91. (set! buffer (get-bytes))
  92. (set! buffer-pos 0)))
  93. (let ((to-copy (min count (- (bytevector-length buffer) buffer-pos))))
  94. (bytevector-copy! buffer buffer-pos bv start to-copy)
  95. (if (= (bytevector-length buffer) (+ buffer-pos to-copy))
  96. (set! buffer #f)
  97. (set! buffer-pos (+ buffer-pos to-copy)))
  98. to-copy))
  99. (define (%flush-input)
  100. (get-bytes)
  101. (set! buffer #f))
  102. (values %read %flush-input))
  103. (define (custom-textual-port-write write)
  104. (unless (procedure? write)
  105. (type-error "custom-textual-port-write" "procedure" write))
  106. (lambda (port bv start count)
  107. (let* ((bytes (bytevector-slice bv start count))
  108. (str (call-with-input-bytevector
  109. bytes
  110. (lambda (bport)
  111. (set-port-encoding! bport (port-encoding port))
  112. (set-port-conversion-strategy!
  113. bport
  114. (port-conversion-strategy port))
  115. (get-string-all bport))))
  116. (len (string-length str)))
  117. (let lp ((written 0))
  118. (cond
  119. ((= written len) count)
  120. (else
  121. (let ((to-write (- len written)))
  122. (let ((res (write str written to-write)))
  123. (unless (and (exact-integer? res) (<= 0 res to-write))
  124. (scm-error 'out-of-range "custom-textual-port-write"
  125. "Value out of range: ~S" (list res) (list res)))
  126. (lp (+ written res))))))))))
  127. (define (custom-textual-port-seek get-position set-position! flush-input)
  128. (when get-position
  129. (unless (procedure? get-position)
  130. (type-error "custom-textual-port-seek" "procedure" get-position)))
  131. (when set-position!
  132. (unless (procedure? set-position!)
  133. (type-error "custom-textual-port-seek" "procedure" set-position!)))
  134. (define (seek port offset whence)
  135. (cond
  136. ((eqv? whence SEEK_CUR)
  137. (unless get-position
  138. (type-error "custom-textual-port-seek"
  139. "R6RS custom textual port with `port-position` support"
  140. port))
  141. (if (zero? offset)
  142. (get-position)
  143. (seek port (+ (get-position) offset) SEEK_SET)))
  144. ((eqv? whence SEEK_SET)
  145. (unless set-position!
  146. (type-error "custom-textual-port-seek"
  147. "Seekable R6RS custom textual port"
  148. port))
  149. (flush-input)
  150. (set-position! offset)
  151. ;; Assume setting the position succeeds.
  152. offset)
  153. ((eqv? whence SEEK_END)
  154. (error "R6RS custom textual ports do not support `SEEK_END'"))))
  155. seek)
  156. (define (custom-textual-port-close close)
  157. (match close
  158. (#f (lambda (port) #t))
  159. ((? procedure?) (lambda (port) (close)))
  160. (_ (type-error "custom-textual-port-close" "procedure" close))))
  161. (define (custom-textual-port-random-access? set-position!)
  162. (if set-position!
  163. (lambda (port) #t)
  164. (lambda (port) #f)))
  165. (define (make-custom-textual-input-port id read get-position set-position!
  166. close)
  167. (unless (string? id)
  168. (type-error "make-custom-textual-input-port" "string" id))
  169. (define-values (%read %flush-input)
  170. (custom-textual-port-read+flush-input read))
  171. (make-custom-port #:id id
  172. #:read %read
  173. #:seek (custom-textual-port-seek get-position set-position!
  174. %flush-input)
  175. #:close (custom-textual-port-close close)
  176. #:random-access?
  177. (custom-textual-port-random-access? set-position!)))
  178. (define (make-custom-textual-output-port id write get-position set-position!
  179. close)
  180. (unless (string? id)
  181. (type-error "make-custom-textual-output-port" "string" id))
  182. (define (flush-input) #t)
  183. (make-custom-port #:id id
  184. #:write (custom-textual-port-write write)
  185. #:seek (custom-textual-port-seek get-position set-position!
  186. flush-input)
  187. #:close (custom-textual-port-close close)
  188. #:random-access?
  189. (custom-textual-port-random-access? set-position!)))
  190. (define (make-custom-textual-input/output-port id read write get-position
  191. set-position! close)
  192. (unless (string? id)
  193. (type-error "make-custom-textual-input/output-port" "string" id))
  194. (define-values (%read %flush-input)
  195. (custom-textual-port-read+flush-input read))
  196. (make-custom-port #:id id
  197. #:read %read
  198. #:write (custom-textual-port-write write)
  199. #:seek (custom-textual-port-seek get-position set-position!
  200. %flush-input)
  201. #:close (custom-textual-port-close close)
  202. #:random-access?
  203. (custom-textual-port-random-access? set-position!)))