ports.scm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. ;;;; ports.scm --- R6RS port API -*- coding: utf-8 -*-
  2. ;;;; Copyright (C) 2009-2011, 2013, 2019, 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. ;;; Author: Ludovic Courtès <ludo@gnu.org>
  18. ;;; Commentary:
  19. ;;;
  20. ;;; The I/O port API of the R6RS is provided by this module. In many areas
  21. ;;; it complements or refines Guile's own historical port API. For instance,
  22. ;;; it allows for binary I/O with bytevectors.
  23. ;;;
  24. ;;; Code:
  25. (library (rnrs io ports (6))
  26. (export eof-object eof-object?
  27. ;; auxiliary types
  28. file-options buffer-mode buffer-mode?
  29. eol-style native-eol-style error-handling-mode
  30. make-transcoder transcoder-codec transcoder-eol-style
  31. transcoder-error-handling-mode native-transcoder
  32. latin-1-codec utf-8-codec utf-16-codec
  33. ;; transcoding bytevectors
  34. bytevector->string string->bytevector
  35. ;; input & output ports
  36. port? input-port? output-port?
  37. port-eof?
  38. port-transcoder binary-port? textual-port? transcoded-port
  39. port-position set-port-position!
  40. port-has-port-position? port-has-set-port-position!?
  41. call-with-port close-port
  42. ;; input ports
  43. open-bytevector-input-port
  44. open-string-input-port
  45. open-file-input-port
  46. make-custom-binary-input-port
  47. make-custom-textual-input-port
  48. ;; binary input
  49. get-u8 lookahead-u8
  50. get-bytevector-n get-bytevector-n!
  51. get-bytevector-some get-bytevector-all
  52. ;; output ports
  53. open-bytevector-output-port
  54. open-string-output-port
  55. open-file-output-port
  56. make-custom-binary-output-port
  57. call-with-bytevector-output-port
  58. call-with-string-output-port
  59. make-custom-textual-output-port
  60. output-port-buffer-mode
  61. flush-output-port
  62. ;; input/output ports
  63. open-file-input/output-port
  64. make-custom-binary-input/output-port
  65. make-custom-textual-input/output-port
  66. ;; binary output
  67. put-u8 put-bytevector
  68. ;; textual input
  69. get-char get-datum get-line get-string-all get-string-n get-string-n!
  70. lookahead-char
  71. ;; textual output
  72. put-char put-datum put-string
  73. ;; standard ports
  74. standard-input-port standard-output-port standard-error-port
  75. current-input-port current-output-port current-error-port
  76. ;; condition types
  77. &i/o i/o-error? make-i/o-error
  78. &i/o-read i/o-read-error? make-i/o-read-error
  79. &i/o-write i/o-write-error? make-i/o-write-error
  80. &i/o-invalid-position i/o-invalid-position-error?
  81. make-i/o-invalid-position-error
  82. &i/o-filename i/o-filename-error? make-i/o-filename-error
  83. i/o-error-filename
  84. &i/o-file-protection i/o-file-protection-error?
  85. make-i/o-file-protection-error
  86. &i/o-file-is-read-only i/o-file-is-read-only-error?
  87. make-i/o-file-is-read-only-error
  88. &i/o-file-already-exists i/o-file-already-exists-error?
  89. make-i/o-file-already-exists-error
  90. &i/o-file-does-not-exist i/o-file-does-not-exist-error?
  91. make-i/o-file-does-not-exist-error
  92. &i/o-port i/o-port-error? make-i/o-port-error
  93. i/o-error-port
  94. &i/o-decoding i/o-decoding-error?
  95. make-i/o-decoding-error
  96. &i/o-encoding i/o-encoding-error?
  97. make-i/o-encoding-error i/o-encoding-error-char)
  98. (import (ice-9 binary-ports)
  99. (only (ice-9 textual-ports)
  100. make-custom-textual-input-port
  101. make-custom-textual-output-port
  102. make-custom-textual-input/output-port)
  103. (only (rnrs base) assertion-violation)
  104. (only (ice-9 ports internal)
  105. port-write-buffer port-buffer-bytevector port-line-buffered?)
  106. (only (rnrs bytevectors) bytevector-length)
  107. (prefix (ice-9 iconv) iconv:)
  108. (rnrs enums)
  109. (rnrs records syntactic)
  110. (rnrs exceptions)
  111. (rnrs conditions)
  112. (rnrs files) ;for the condition types
  113. (srfi srfi-8)
  114. (ice-9 rdelim)
  115. (except (guile) raise display)
  116. (prefix (only (guile) display)
  117. guile:))
  118. ;;;
  119. ;;; Auxiliary types
  120. ;;;
  121. (define-enumeration file-option
  122. (no-create no-fail no-truncate)
  123. file-options)
  124. (define-enumeration buffer-mode
  125. (none line block)
  126. buffer-modes)
  127. (define (buffer-mode? symbol)
  128. (enum-set-member? symbol (enum-set-universe (buffer-modes))))
  129. (define-enumeration eol-style
  130. (lf cr crlf nel crnel ls none)
  131. eol-styles)
  132. (define (native-eol-style)
  133. (eol-style none))
  134. (define-enumeration error-handling-mode
  135. (ignore raise replace)
  136. error-handling-modes)
  137. (define-record-type (transcoder %make-transcoder transcoder?)
  138. (fields codec eol-style error-handling-mode))
  139. (define* (make-transcoder codec
  140. #:optional
  141. (eol-style (native-eol-style))
  142. (handling-mode (error-handling-mode replace)))
  143. (%make-transcoder codec eol-style handling-mode))
  144. (define (native-transcoder)
  145. (make-transcoder (or (fluid-ref %default-port-encoding)
  146. (latin-1-codec))))
  147. (define (latin-1-codec)
  148. "ISO-8859-1")
  149. (define (utf-8-codec)
  150. "UTF-8")
  151. (define (utf-16-codec)
  152. "UTF-16")
  153. ;;;
  154. ;;; Transcoding bytevectors
  155. ;;;
  156. (define (string->bytevector str transcoder)
  157. "Encode @var{str} using @var{transcoder}, returning a bytevector."
  158. (iconv:string->bytevector
  159. str
  160. (transcoder-codec transcoder)
  161. (case (transcoder-error-handling-mode transcoder)
  162. ((raise) 'error)
  163. ((replace) 'substitute)
  164. (else (error "unsupported error handling mode"
  165. (transcoder-error-handling-mode transcoder))))))
  166. (define (bytevector->string bv transcoder)
  167. "Decode @var{bv} using @var{transcoder}, returning a string."
  168. (iconv:bytevector->string
  169. bv
  170. (transcoder-codec transcoder)
  171. (case (transcoder-error-handling-mode transcoder)
  172. ((raise) 'error)
  173. ((replace) 'substitute)
  174. (else (error "unsupported error handling mode"
  175. (transcoder-error-handling-mode transcoder))))))
  176. ;;;
  177. ;;; Internal helpers
  178. ;;;
  179. (define (with-i/o-filename-conditions filename thunk)
  180. (with-throw-handler 'system-error
  181. thunk
  182. (lambda args
  183. (let ((errno (system-error-errno args)))
  184. (let ((construct-condition
  185. (cond ((= errno EACCES)
  186. make-i/o-file-protection-error)
  187. ((= errno EEXIST)
  188. make-i/o-file-already-exists-error)
  189. ((= errno ENOENT)
  190. make-i/o-file-does-not-exist-error)
  191. ((= errno EROFS)
  192. make-i/o-file-is-read-only-error)
  193. (else
  194. make-i/o-filename-error))))
  195. (raise (construct-condition filename)))))))
  196. (define (with-i/o-port-error port make-primary-condition thunk)
  197. (with-throw-handler 'system-error
  198. thunk
  199. (lambda args
  200. (let ((errno (system-error-errno args)))
  201. (if (memv errno (list EIO EFBIG ENOSPC EPIPE))
  202. (raise (condition (make-primary-condition)
  203. (make-i/o-port-error port)))
  204. (apply throw args))))))
  205. (define-syntax with-textual-output-conditions
  206. (syntax-rules ()
  207. ((_ port body0 body ...)
  208. (with-i/o-port-error port make-i/o-write-error
  209. (lambda () (with-i/o-encoding-error body0 body ...))))))
  210. (define-syntax with-textual-input-conditions
  211. (syntax-rules ()
  212. ((_ port body0 body ...)
  213. (with-i/o-port-error port make-i/o-read-error
  214. (lambda () (with-i/o-decoding-error body0 body ...))))))
  215. ;;;
  216. ;;; Input and output ports.
  217. ;;;
  218. (define (port-transcoder port)
  219. "Return the transcoder object associated with @var{port}, or @code{#f}
  220. if the port has no transcoder."
  221. (and (textual-port? port)
  222. ;; All textual ports have transcoders.
  223. (make-transcoder
  224. (port-encoding port)
  225. (native-eol-style)
  226. (case (port-conversion-strategy port)
  227. ((error) 'raise)
  228. ((substitute) 'replace)
  229. (else
  230. (assertion-violation 'port-transcoder
  231. "unsupported error handling mode"))))))
  232. (define (binary-port? port)
  233. "Return @code{#t} if @var{port} appears to be a binary port, else
  234. return @code{#f}. Note that Guile does not currently distinguish
  235. between binary and textual ports, so this predicate is not a reliable
  236. indicator of whether the port was created as a binary port. Currently,
  237. it returns @code{#t} if and only if the port encoding is ``ISO-8859-1'',
  238. because Guile uses this encoding when creating a binary port."
  239. (equal? (port-encoding port) "ISO-8859-1"))
  240. (define (textual-port? port)
  241. "Return @code{#t} if @var{port} appears to be a textual port, else
  242. return @code{#f}. Note that Guile does not currently distinguish
  243. between binary and textual ports, so this predicate is not a reliable
  244. indicator of whether the port was created as a textual port. Currently,
  245. it always returns @code{#t}, because all ports can be used for textual
  246. I/O in Guile."
  247. #t)
  248. (define (port-eof? port)
  249. (eof-object? (if (binary-port? port)
  250. (lookahead-u8 port)
  251. (lookahead-char port))))
  252. (define (transcoded-port port transcoder)
  253. "Return a new textual port based on @var{port}, using
  254. @var{transcoder} to encode and decode data written to or
  255. read from its underlying binary port @var{port}."
  256. ;; Hackily get at %make-transcoded-port.
  257. (let ((result ((@@ (ice-9 binary-ports) %make-transcoded-port) port)))
  258. (set-port-encoding! result (transcoder-codec transcoder))
  259. (case (transcoder-error-handling-mode transcoder)
  260. ((raise)
  261. (set-port-conversion-strategy! result 'error))
  262. ((replace)
  263. (set-port-conversion-strategy! result 'substitute))
  264. (else
  265. (error "unsupported error handling mode"
  266. (transcoder-error-handling-mode transcoder))))
  267. result))
  268. (define (port-position port)
  269. "Return the offset (an integer) indicating where the next octet will be
  270. read from/written to in @var{port}."
  271. ;; FIXME: We should raise an `&assertion' error when not supported.
  272. (seek port 0 SEEK_CUR))
  273. (define (set-port-position! port offset)
  274. "Set the position where the next octet will be read from/written to
  275. @var{port}."
  276. ;; FIXME: We should raise an `&assertion' error when not supported.
  277. (seek port offset SEEK_SET))
  278. (define (port-has-port-position? port)
  279. "Return @code{#t} is @var{port} supports @code{port-position}."
  280. (and (false-if-exception (port-position port)) #t))
  281. (define (port-has-set-port-position!? port)
  282. "Return @code{#t} is @var{port} supports @code{set-port-position!}."
  283. (and (false-if-exception (set-port-position! port (port-position port)))
  284. #t))
  285. (define* (call-with-bytevector-output-port proc #:optional (transcoder #f))
  286. (receive (port extract) (open-bytevector-output-port transcoder)
  287. (call-with-port port proc)
  288. (extract)))
  289. (define (open-string-input-port str)
  290. "Open an input port that will read from @var{str}."
  291. (open-input-string str))
  292. (define (r6rs-open filename mode buffer-mode transcoder)
  293. (let ((port (with-i/o-filename-conditions filename
  294. (lambda ()
  295. (with-fluids ((%default-port-encoding #f))
  296. (open filename mode))))))
  297. (setvbuf port buffer-mode)
  298. (when transcoder
  299. (set-port-encoding! port (transcoder-codec transcoder)))
  300. port))
  301. (define (file-options->mode file-options base-mode)
  302. (logior base-mode
  303. (if (enum-set-member? 'no-create file-options)
  304. 0
  305. O_CREAT)
  306. (if (enum-set-member? 'no-truncate file-options)
  307. 0
  308. O_TRUNC)
  309. (if (enum-set-member? 'no-fail file-options)
  310. 0
  311. O_EXCL)))
  312. (define* (open-file-input-port filename
  313. #:optional
  314. (file-options (file-options))
  315. (buffer-mode (buffer-mode block))
  316. transcoder)
  317. "Return an input port for reading from @var{filename}."
  318. (r6rs-open filename O_RDONLY buffer-mode transcoder))
  319. (define* (open-file-input/output-port filename
  320. #:optional
  321. (file-options (file-options))
  322. (buffer-mode (buffer-mode block))
  323. transcoder)
  324. "Return a port for reading from and writing to @var{filename}."
  325. (r6rs-open filename
  326. (file-options->mode file-options O_RDWR)
  327. buffer-mode
  328. transcoder))
  329. (define (open-string-output-port)
  330. "Return two values: an output port that will collect characters written to it
  331. as a string, and a thunk to retrieve the characters associated with that port."
  332. (let ((port (open-output-string)))
  333. (values port
  334. (lambda ()
  335. (let ((s (get-output-string port)))
  336. (seek port 0 SEEK_SET)
  337. (truncate-file port 0)
  338. s)))))
  339. (define* (open-file-output-port filename
  340. #:optional
  341. (file-options (file-options))
  342. (buffer-mode (buffer-mode block))
  343. maybe-transcoder)
  344. "Return an output port for writing to @var{filename}."
  345. (r6rs-open filename
  346. (file-options->mode file-options O_WRONLY)
  347. buffer-mode
  348. maybe-transcoder))
  349. (define (call-with-string-output-port proc)
  350. "Call @var{proc}, passing it a string output port. When @var{proc} returns,
  351. return the characters accumulated in that port."
  352. (let ((port (open-output-string)))
  353. (proc port)
  354. (get-output-string port)))
  355. (define (output-port-buffer-mode port)
  356. "Return @code{none} if @var{port} is unbuffered, @code{line} if it is
  357. line buffered, or @code{block} otherwise."
  358. (let ((buffering (bytevector-length
  359. (port-buffer-bytevector (port-write-buffer port)))))
  360. (cond
  361. ((= buffering 1) 'none)
  362. ((port-line-buffered? port) 'line)
  363. (else 'block))))
  364. (define (flush-output-port port)
  365. (force-output port))
  366. ;;;
  367. ;;; Textual output.
  368. ;;;
  369. (define-condition-type &i/o-encoding &i/o-port
  370. make-i/o-encoding-error i/o-encoding-error?
  371. (char i/o-encoding-error-char))
  372. (define-syntax with-i/o-encoding-error
  373. (syntax-rules ()
  374. "Convert Guile throws to `encoding-error' to `&i/o-encoding'."
  375. ((_ body ...)
  376. ;; XXX: This is heavyweight for small functions like `put-char'.
  377. (with-throw-handler 'encoding-error
  378. (lambda ()
  379. (begin body ...))
  380. (lambda (key subr message errno port chr)
  381. (raise (make-i/o-encoding-error port chr)))))))
  382. (define (put-char port char)
  383. (with-textual-output-conditions port (write-char char port)))
  384. (define (put-datum port datum)
  385. (with-textual-output-conditions port (write datum port)))
  386. (define* (put-string port s #:optional start count)
  387. (with-textual-output-conditions port
  388. (cond ((not (string? s))
  389. (assertion-violation 'put-string "expected string" s))
  390. ((and start count)
  391. (display (substring/shared s start (+ start count)) port))
  392. (start
  393. (display (substring/shared s start (string-length s)) port))
  394. (else
  395. (display s port)))))
  396. ;; Defined here to be able to make use of `with-i/o-encoding-error', but
  397. ;; not exported from here, but from `(rnrs io simple)'.
  398. (define* (display object #:optional (port (current-output-port)))
  399. (with-textual-output-conditions port (guile:display object port)))
  400. ;;;
  401. ;;; Textual input.
  402. ;;;
  403. (define-condition-type &i/o-decoding &i/o-port
  404. make-i/o-decoding-error i/o-decoding-error?)
  405. (define-syntax with-i/o-decoding-error
  406. (syntax-rules ()
  407. "Convert Guile throws to `decoding-error' to `&i/o-decoding'."
  408. ((_ body ...)
  409. ;; XXX: This is heavyweight for small functions like `get-char' and
  410. ;; `lookahead-char'.
  411. (with-throw-handler 'decoding-error
  412. (lambda ()
  413. (begin body ...))
  414. (lambda (key subr message errno port)
  415. (raise (make-i/o-decoding-error port)))))))
  416. (define (get-char port)
  417. (with-textual-input-conditions port (read-char port)))
  418. (define (get-datum port)
  419. (with-textual-input-conditions port (read port)))
  420. (define (get-line port)
  421. (with-textual-input-conditions port (read-line port 'trim)))
  422. (define (get-string-all port)
  423. (with-textual-input-conditions port (read-string port)))
  424. (define (get-string-n port count)
  425. "Read up to @var{count} characters from @var{port}.
  426. If no characters could be read before encountering the end of file,
  427. return the end-of-file object, otherwise return a string containing
  428. the characters read."
  429. (let* ((s (make-string count))
  430. (rv (get-string-n! port s 0 count)))
  431. (cond ((eof-object? rv) rv)
  432. ((= rv count) s)
  433. (else (substring/shared s 0 rv)))))
  434. (define (lookahead-char port)
  435. (with-textual-input-conditions port (peek-char port)))
  436. ;;;
  437. ;;; Standard ports.
  438. ;;;
  439. (define (standard-input-port)
  440. (with-fluids ((%default-port-encoding #f))
  441. (dup->inport 0)))
  442. (define (standard-output-port)
  443. (with-fluids ((%default-port-encoding #f))
  444. (dup->outport 1)))
  445. (define (standard-error-port)
  446. (with-fluids ((%default-port-encoding #f))
  447. (dup->outport 2)))
  448. )
  449. ;;; ports.scm ends here