racket/s/io.ss
dybvig 7d145e37a8 Various enhancements and fixes highlighted by profiling performance
and functionality improvements (including support for measuring
coverage), primitive argument-checking fixes, and object-file changes
resulting in reduced load times (and some backward incompatibility):
- annotations are now preserved in object files for debug
  only, for profiling only, for both, or not at all, depending
  on the settings of generate-inspector-information and
  compile-profile.  in particular, when inspector information
  is not enabled but profiling is, source information does
  not leak into error messages and inspector output, though it is
  still available via the profile tools.  The mechanics of this
  involved repurposing the fasl a? parameter to hold an annotation
  flags value when it is not #f and remaking annotations with
  new flags if necessary before emitting them.
    compile.ss, fasl.ss, misc.ms
- altered a number of mats to produce correct results even
  when the 's' directory is profiled.
    misc.ms, cp0.ms, record.ms
- profile-release-counters is now generation-friendly; that is,
  it doesn't look for dropped code objects in generations that have
  not been collected since the last call to profile-release-counters.
  also, it no longer allocates memory when it releases counters.
    pdhtml.ss,
    gc.c, gcwrapper.c, globals.h, prim5.c
- removed unused entry points S_ifile, S_ofile, and S_iofile
    alloc.c, externs.h
- mats that test loading profile info into the compiler's database
  to guide optimization now weed out preexisting entries, in case
  the 's' directory is profiled.
    4.ms, mat.ss, misc.ms, primvars.ms
- counters for dropped code objects are now released at the start
  of each mat group.
    mat.ss
- replaced ehc (enable-heap-check) option with hci (heap-check-interval)
  option that allows heap checks to be performed periodically rather
  than on each collection.  hci=0 is equivalent to ehc=f (disabling
  heap checks) and hci=1 is equivalent to ehc=t (enabling heap
  checks every collection), while hci=100 enables heap checks only
  every 100th collection.  allx and bullyx mats use this feature
  to reduce heap-checking overhead to a more reasonable level.  this
  is particularly important when the 's' directory is profiled,
  since the amount of static memory to be checked is greatly increased
  due to the counters.
    mats/Mf-base, mat.ss, primvars.ms
- added a mat that calls #%show-allocation, which was otherwise not
  being tested.
    misc.ms
- removed a broken primvars mat and updated two others.  in each case,
  the mat was looking for information about primitives in the wrong
  (i.e., old) place and silently succeeding when it didn't find any
  primitives to tests.  the revised mats (along with a few others) now
  check to make sure at least one identifier has the information they
  look for.  the removed mat was checking for library information that
  is now compiled in, so the mat is now unnecessary.  the others were
  (not) doing argument-error checks.  fixing these turned up a handful of
  problems that have also been fixed: a couple of unbound variables in the
  mat driver, two broken primdata declarations, a tardy argument check
  by profile-load-data, and a bug in char-ready?, which was requiring
  an argument rather than defaulting it to the current input port.
    primdata.ss, pdhtml.ss, io.ms,
    primdvars.ms, 4.ms, 6.ms, misc.ms, patch*
- added initial support for recording coverage information.  when the
  new parameter generate-covin-files is set, the compiler generates
  .covin files containing the universe of all source objects for which
  profile forms are present in the expander output.  when profiling
  and generation of covin files are enabled in the 's' directory, the
  mats optionally generate .covout files for each mat file giving
  the subset of the universe covered by the mat file, along with an
  all.covout in each mat output directory aggregating the coverage
  for the directory and another all.covout in the top-level mat
  directory aggregating the coverage for all directories.
    back.ss, compile.ss, cprep.ss, primdata.ss, s/Mf-base,
    mat.ss, mats/Mf-base, mats/primvars.ms
- support for generating covout files is now built in.  with-coverage-output
  gathers and dumps coverage information, and aggregate-coverage-output
  combines (aggregates) covout files.
    pdhtml.ss, primdata.ss, compile.ss,
    mat.ss, mats/Mf-base, primvars.ms
- profile-clear now adjusts active coverage trackers to avoid losing
  coverage information.
    pdhtml.ss,
    prim5.c
- nested with-coverage calls are now supported.
    pdhtml.ss
- switched to a more compact representation for covin and covout files;
  reduces disk space (compressed or not) by about a factor of four
  and read time by about a factor of two with no increase in write time.
    primdata.ss, pdhtml.ss, cprep.ss, compile.ss,
    mat.ss, mats/Mf-base
- added support for determining coverage for an entire run, including
  coverage for expressions hit during boot time.  'all' mats now produce
  run.covout files in each output directory, and 'allx' mats produce
  an aggregate run.covout file in the mat directory.
    pdhtml.ss,
    mat.ss, mats/Mf-base
- profile-release-counters now adjusts active coverage trackers to
  account for the counters that have been released.
    pdhtml.ss,
    prim5.c
- replaced the artificial "examples" target with a real "build-examples"
  target so make won't think it always has to mats that depend upon
  the examples directory having been compiled.  mats make clean now
  runs make clean in the examples directory.
    mats/Mf-base
  importing a library from an object file now just visits the object
  file rather than doing a full load so that the run-time code for
  the library is not retained.  The run-time code is still read
  because the current fasl format forces the entire file to be read,
  but not retaining the code can lower heap size and garbage-collection
  cost, particularly when many object-code libraries are imported.
  The downside is that the file must be revisited if the run-time
  code turns out to be required.   This change exposed several
  places where the code was failing to check if a revisit is needed.
    syntax.ss,
    7.ms, 8.ms, misc.ms, root-experr*
- fixed typos: was passing unquoted load rather than quoted load
  to $load-library along one path (where it is loading source code
  and therefore irrelevant), and was reporting src-path rather than
  obj-path in a message about failing to define a library.
    syntax.ss
- compile-file and friends now put all recompile information in
  the first fasl object after the header so the library manager can
  find it without loading the entire fasl file.  The library manager
  now does so.  It also now checks to see if library object files
  need to be recreated before loading them rather than loading them and
  possibly recompiling them after discovering they are out of date, since
  the latter requires loading the full object file even if it's out of
  date, while the former takes advantage of the ability to extract just
  recompile information.  as well as reducing overhead, this eliminates
  possibly undesirable side effects, such as creation and registration
  of out-of-date nongenerative record-type descriptors.  because the
  library manager expects to find recompile information at the front of
  an object file, it will not find all recompile information if object
  files are "catted" together.  also, compile-file has to hold in memory
  the object code for all expressions in the file so that it can emit the
  unified recompile information, rather than writing to the object file
  incrementally, which can significantly increase the memory required
  to compile a large file full of individual top-level forms.  This does
  not affect top-level programs, which were already handled as a whole,
  or a typical library file that contains just a single library form.
    compile.ss, syntax.ss
- the library manager now checks include files before library dependencies
  when compile-imported-libraries is false (as it already did when
  compile-imported-libraries is true) in case a source change affects
  the set of imported libraries.  (A library change can affect the set
  of include files as well, but checking dependencies before include
  files can cause unneeded libraries to be loaded.)  The include-file
  check is based on recompile-info rather than dependencies, but the
  library checks are still based on dependencies.
    syntax.ss
- fixed check for binding of scheme-version. (the check prevents
  premature treatment of recompile-info records as Lexpand forms
  to be passed to $interpret-backend.)
    scheme.c
- strip-fasl-file now preserves recompile-info when compile-time info
  is stripped.
    strip.ss
- removed include-req* from library/ct-info and ctdesc records; it
  is no longer needed now that all recompile information is maintained
  separately.
    expand-lang.ss, syntax.ss, compile.ss, cprep.ss, syntax.ss
- changed the fasl format and reworked a lot of code in the expander,
  compiler, fasl writer, and fasl reader to allow the fasl reader
  to skip past run-time information when it isn't needed and
  compile-time information when it isn't needed.  Skipping past
  still involves reading and decoding when encrypted, but the fasl
  reader no longer parses or allocates code and data in the portions
  to be skipped.  Side effects of associating record uids with rtds
  are also avoided, as are the side effects of interning symbols
  present only in the skipped data.  Skipping past code objects
  also reduces or eliminates the need to synchronize data and
  instruction caches.  Since the fasl reader no longer returns
  compile-time (visit) or run-time (revisit) code and data when not
  needed, the fasl reader no longer wraps these objects in a pair
  with a 0 or 1 visit or revisit marker.  To support this change,
  the fasl writer generates separate top-level fasl entries (and
  graphs) for separate forms in the same top-level source form
  (e.g., begin or library).  This reliably breaks eq-ness of shared
  structure across these forms, which was previously broken only
  when visit or revisit code was loaded at different times (this
  is an incompatible change).  Because of the change, fasl "groups"
  are no longer needed, so they are no longer handled.
    7.ss, cmacros.ss, compile.ss, expand-lang.ss, strip.ss,
    externs.h, fasl.c, scheme.c,
    hash.ms
- the change above is surfaced in an optional fasl-read "situation"
  argument (visit, revisit, or load).  The default is load.  visit
  causes it to skip past revisit code and data; revisit causes it
  to skip past visit code and data; and load causes it not to skip
  past either.  visit-revisit data produced by (eval-when (visit
  revisit) ---) is never skipped.
    7.ss, primdata.ss,
    io.stex
- to improve compile-time and run-time error checking, the
  Lexpand recompile-info, library/rt-info, library-ct-info, and
  program-info forms have been replaced with list-structured forms,
  e.g., (recompile-info ,rcinfo).
    expand-lang.ss, compile.ss, cprep.ss, interpret.ss, syntax.ss
- added visit-compiled-from-port and revisit-compiled-from-port
  to complement the existing load-compiled-from-port.
    7.ss, primdata.ss,
    7.ms,
    system.stex
- increased amount read when seeking an lz4-encrypted input
  file from 32 to 1024 bytes at a time
    compress-io.c
- replaced the fasl a? parameter value #t with an "all" flag value
  so it's value is consistently a mask.
    cmacros.ss, fasl.ss, compile.ss
- split off profile mats into a separate file
    misc.ms, profile.ms (new), root-experr*, mats/Mf-base
- added coverage percent computations to mat allx/bullyx output
    mat.ss, mats/Mf-base, primvars.ms
- replaced coverage tables with more generic and generally useful
  source tables, which map source objects to arbitrary values.
    pdhtml.ss, compile.ss, cprep.ss, primdata.ss,
    mat.ss, mats/Mf-base, primvars.ms, profile.ms,
    syntax.stex
- reduced profile counting overhead by using calls to fold-left
  instead of calls to apply and map and by using fixnum operations
  for profile counts on 64-bit machines.
    pdhtml.ss
- used a critical section to fix a race condition in the calculations
  of profile counts that sometimes resulted in bogus (including
  negative) counts, especially when the 's' directory is profiled.
    pdhtml.ss
- added discard flag to declaration for hashtable-size
    primdata.ss
- redesigned the printed representation of source tables and rewrote
  get-source-table! to read and store incrementally to reduce memory
  overhead.
    compile.ss
- added generate-covin-files to the set of parameters preserved
  by compile-file, etc.
    compile.ss,
    system.stex
- moved covop argument before the undocumented machine and hostop
  arguments to compile-port and compile-to-port.  removed the
  undocumented ofn argument from compile-to-port; using
  (port-name ip) instead.
    compile.ss, primdata.ss,
    7.ms,
    system.stex
- compile-port now tries to come up with a file position to supply
  to make-read, which it can do if the port's positions are character
  positions (presently string ports) or if the port is positioned
  at zero.
    compile.ss
- audited the argument-type-error fuzz mat exceptions and fixed a
  host of problems this turned up (entries follow).  added #f as
  an invalid argument for every type for which #f is indeed invalid
  to catch places where the maybe- prefix was missing on the argument
  type.  the mat tries hard to determine if the condition raised
  (if any) as the result of an invalid argument is appropriate and
  redirects the remainder to the mat-output (.mo) file prefixed
  with 'Expected error', causing them to show up in the expected
  error output so developers will be encouraged to audit them in
  the future.
    primvars.ms, mat.ss
- added an initial symbol? test on machine type names so we produce
  an invalid machine type error message rather than something
  confusing like "machine type #f is not supported".
    compile.ss
- fixed declarations for many primitives that were specified as
  accepting arguments of more general types than they actually
  accept, such as number -> real for various numeric operations,
  symbol -> endianness for various bytevector operations,
  time -> time-utc for time-utc->date, and list -> list-of-string-pairs
  for default-library-search-handler.   also replaced some of the
  sub-xxxx types with specific types such as sub-symbol -> endianness
  in utf16->string, but only where they were causing issues with
  the primvars argument-type-error fuzz mat.  (this should be done
  more generally.)
    primdata.ss
- fixed incorrect who arguments (was map instead of fold-right,
  current-date instead of time-utc->date); switched to using
  define-who/set-who! generally.
    4.ss, date.ss
- append! now checks all arguments before any mutation
    5_2.ss
- with-source-path now properly supplies itself as who for the
  string? argument check; callers like load now do their own checks.
    7.ss
- added missing integer? check to $fold-bytevector-native-ref whose
  lack could have resulted in a compile-time error.
    cp0.ss
- fixed typo in output-port-buffer-mode error message
    io.ss
- fixed who argument (was fx< rather than fx<?)
    library.ss
- fixed declaration of first source-file-descriptor argument (was
  sfd, now string)
    primdata.ss
- added missing article 'a' in a few error messages
    prims.ss
- fixed the copy-environment argument-type error message for the list
  of symbols argument.
    syntax.ss
- the environment procedure now catches exceptions that occur and
  reraises the exception with itself as who if the condition isn't
  already a who condition.
    syntax.ss
- updated experr and allx patch files for changes to argument-count
  fuzz mat and fixes for problems turned up by them.
    root-experr*, patch*
- fixed a couple of issues setting port sizes: string and bytevector
  output port put handlers don't need room to store the character
  or byte, so they now set the size to the buffer length rather
  than one less.  binary-file-port-clear-output now sets the index
  rather than size to zero; setting the size to zero is inappropriate
  for some types of ports and could result in loss of buffering and
  even suppression of future output.  removed a couple of redundant
  sets of the size that occur immediately after setting the buffer.
    io.ss
- it is now possible to return from a call to with-profile-tracker
  multiple times and not double-count (or worse) any counts.
    pdhtml.ss, profile.ms
- read-token now requires a file position when it is handed a
  source-file descriptor (since the source-file descriptor isn't
  otherwise useful), and the source-file descriptor argument can
  no longer be #f.  the input file position plays the same role as
  the input file position in get-datum/annotations.  these extra
  read-token arguments are now documented.
    read.ss,
    6.ms,
    io.stex
- the source-file descriptor argument to get-datum/annotations can
  no longer be #f.  it was already documented that way.
    read.ss
- read-token and do-read now look for the character-positions port
  flag before asking if the port has port-position, since the latter
  is slightly more expensive.
    read.ss
- rd-error now reports the current port position if it can be determined
  when fp isn't already set, i.e., when reading from a port without
  character positions (presently any non string port) and fp has not
  been passed in explicitly (to read-token or get-datum/annotations).
  the port position might not be a character position, but it should be
  better than nothing.
    read.ss
- added comment noting an invariant for s_profile_release_counters.
    prim5.c
- restored accidentally dropped fasl-write formdef and dropped
  duplicate fasl-read formdef
    io.stex
- added a 'coverage' target that tests the coverage of the Scheme-code
  portions of Chez Scheme by the mats.
    Makefile.in, Makefile-workarea.in
- added .PHONY declarations for all of the targets in the top-level
  and workarea make files, and renamed the create-bintar, create-rpm,
  and create-pkg targets bintar, rpm, and pkg.
    Makefile.in, Makefile-workarea.in
- added missing --retain-static-relocation command-line argument and
  updated the date
    scheme.1.in
- removed a few redundant conditional variable settings
    configure
- fixed declaration of condition wait (timeout -> maybe-timeout)
    primdata.ss

original commit: 88501743001393fa82e89c90da9185fc0086fbcb
2019-09-21 15:37:29 -07:00

6310 lines
285 KiB
Scheme

"io.ss"
;;; io.ss
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;; http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.
;;; possible extensions:
;;; - mechanism for overriding default #o666 mode
;;; - user-defined handler records
;;; - say user-supplied handler procedures "should" return appropriate
;;; values (e.g., octet/eof for get on binary port), wrap procedures
;;; in return-value checkers, or allow user to choose whether
;;; procedures are wrapped in return-value checkers
;;; r6rs custom ports are fubar:
;;; - binary and textual output ports: no known problems
;;; - binary input ports: no problem except just after a
;;; lookahead-u8 returns #!eof or just after unget-u8 of #!eof,
;;; at which point port position is ill-defined.
;;; - binary input/output ports: can't work without working
;;; get-position and set-position! procedures to switch between
;;; input and output mode
;;; - textual input ports: no way to implement port-position,
;;; since get-position returns an arbitrary object, no way to adjust for
;;; amount we've buffered, and we must buffer at least one character to
;;; support lookahead-char. also same problem as custom binary input
;;; ports with #!eof.
;;; - textual input/output ports: no way to switch between input
;;; and output modes, since we cannot implement port-position.
;;;
;;; all problems derive from need to buffer at least one element to
;;; support lookahead-u8 and lookahead-char.
;;;
;;; our workarounds:
;;; - custom binary and textual output ports:
;;; - none
;;; - custom binary input ports:
;;; - treat eof as zero width
;;; - assume sequential indices from get-position to compute port-position
;;; with adjustment for buffered characters
;;; - custom textual input ports:
;;; - treat eof as zero width
;;; - port-position undefined after read
;;; - no warning for port-position if:
;;; - no reads (including lookahead and port-eof?) have been done
;;; - a set-port-position! occurred after last read
;;; - buffer-mode is none and last read operation was not a lookahead,
;;; port-eof?, or unget
;;; - custom binary or textual input/output ports:
;;; - position for write undefined after read
;;; - port-position undefined after read
;;; - no warning for write or port-position if:
;;; - no reads (including lookahead and port-eof?) have been done
;;; - a write or set-port-position occurred after last read
;;; - buffer-mode is none and last read operation was not a lookahead,
;;; port-eof?, or unget (efficient input can be had with buffer-mode
;;; none if only get-bytevector operations are used. sequence of
;;; gets will relatively slow with buffer-mode none.)
;;; - exception: we use supplied get-position and
;;; set-position! on a custom binary input/output port to sync
;;; position and avoid issuing warnings under assumption that
;;; get-position indices are sequential
#|
implementation notes:
- for binary input/output file ports, we can always distinguish input
mode from output mode by the fact that output-size is zero iff port is
in input mode. this does not work for textual ports, because
output-size can be zero even in output mode for line-buffered ports.
so we instead use an input-mode flag in the port header.
|#
(set-who! file-buffer-size
($make-thread-parameter $c-bufsiz
(lambda (x)
(unless (and (fixnum? x) (fx> x 0))
($oops who "~s is not a positive fixnum" x))
x)))
(set-who! custom-port-buffer-size
($make-thread-parameter 128
(lambda (x)
(unless (and (fixnum? x) (fx> x 0))
($oops who "~s is not a positive fixnum" x))
x)))
(let ()
; choose whether to issue warnings when custom-port implementation
; cannot determine position for port-position or write operation
#;(define position-warning warning)
(define (position-warning who msg . args) (void))
(include "io-types.ss")
(define-syntax call-port-handler
(lambda (x)
(syntax-case x ()
[(_ msg who ?p arg ...)
(identifier? #'msg)
(with-syntax ([port-handler-msg (construct-name #'msg "port-handler-" #'msg)])
#'(let ([p ?p]) ((port-handler-msg ($port-handler p)) who p arg ...)))])))
(define-port-handler (codec make-codec codec?) #f
(name -> string)
(make-info who tx bp bv -> codec-info))
; ioffsets is an fxvector mapping character positions in a port's input buffer
; to byte offsets from the starting byte position for the buffer. ibytes
; is the byte offset of the character just beyond the end of the buffer,
; which is also the length in bytes of the data represented by the characters
; in the buffer. ioffsets and ibytes together allow port positions to be
; reported in bytes. ioffsets and ibytes are not consulted when a port's
; input buffer is empty, so there is no harm in modifying them when reading
; into a different string. since ioffsets might not have as many elements
; as the different string, however, a codec should usually avoid modifying
; ioffsets to prevent writes beyond the end of the vector. a codec's encode
; procedure is always called with start = 0 when string to fill is the port's
; input buffer, so ibytes should also start at 0.
(define-record-type codec-info
(nongenerative)
(opaque #t)
(fields
(immutable tx) ; transcoder
(mutable bp) ; binary port (clone)
(immutable bv) ; bytevector buffer (input or output, one at a time)
(mutable next) ; next pointer into buffer
(mutable iend) ; end of data (input only)
(immutable ioffsets) ; byte offset each char in port's buffer, relative to first (input only)
(mutable ibytes) ; byte offset of first char beyond port's buffer (input only)
(mutable icr) ; #\return seen when eol style is not none (input only)
(mutable bom) ; looking for byte-order-mark on input, or ready to write it on output
(mutable zbom) ; bom found or placed at position zero
(mutable big) ; big endian?
(immutable decode) ; input decoder
(immutable encode) ; output encoder
(immutable close)))
; keep make-fd in sync with types.h MAKE_FD
(define (make-fd intfd) intfd)
(define (port-oops who p msg)
($oops/c who
(make-i/o-port-error p)
"failed on ~s: ~(~a~)" p msg))
(define (read-oops who p msg)
($oops/c who
(condition (make-i/o-read-error) (make-i/o-port-error p))
"failed on ~s: ~(~a~)" p msg))
(define (write-oops who p msg)
($oops/c who
(condition (make-i/o-write-error) (make-i/o-port-error p))
"failed on ~s: ~(~a~)" p msg))
(define (position-oops who p pos msg)
($oops/c who
(condition
(make-i/o-invalid-position-error pos)
(make-i/o-port-error p))
"failed for position ~s on ~s: ~(~a~)" pos p msg))
(define (open-oops who filename file-options err.msg)
($oops/c who
(let ([err (car err.msg)])
(cond
[(eqv? err (constant OPEN-ERROR-PROTECTION))
(make-i/o-file-protection-error filename)]
[(eqv? err (constant OPEN-ERROR-EXISTS))
(make-i/o-file-already-exists-error filename)]
[(eqv? err (constant OPEN-ERROR-EXISTSNOT))
(make-i/o-file-does-not-exist-error filename)]
[else (make-i/o-filename-error filename)]))
"failed for ~a: ~(~a~)"
filename
(cdr err.msg)))
(define (unget-error who p x)
($oops who "cannot unget ~s on ~s" x p))
(define eol-char?
(lambda (c)
(memv c '(#\newline #\return #\nel #\ls))))
(define-syntax port-gz-mode
(syntax-rules ()
[(_ port) ($port-flags-set? port (constant port-flag-compressed))]))
(define-syntax port-flag-eof-set?
(syntax-rules ()
[(_ port) ($port-flags-set? port (constant port-flag-eof))]))
(define-syntax assert-not-closed
(syntax-rules ()
[(_ who port)
(when (port-closed? port)
($oops who "not permitted on closed port ~s" port))]))
(define-syntax file-options-list
(syntax-rules ()
[(_)
'(no-create no-fail no-truncate compressed replace exclusive append
perm-set-user-id perm-set-group-id perm-sticky
perm-no-user-read perm-no-user-write perm-user-execute
perm-no-group-read perm-no-group-write perm-group-execute
perm-no-other-read perm-no-other-write perm-other-execute)]))
(define-syntax eol-style-list
(syntax-rules ()
[(_) '(lf cr crlf nel crnel ls none)]))
(define-syntax error-handling-mode-list
(syntax-rules ()
[(_) '(ignore raise replace)]))
(define ($textual-port-bol? p)
(let ([index (textual-port-output-index p)])
(if (fx= index 0)
($port-flags-set? p (constant port-flag-bol))
(eol-char? (string-ref (textual-port-output-buffer p) (fx- index 1))))))
(define-record-type (transcoder $make-transcoder $transcoder?)
(nongenerative)
(opaque #t)
(sealed #t)
(fields
(immutable codec $transcoder-codec)
(immutable eol-style $transcoder-eol-style)
(immutable error-handling-mode $transcoder-error-handling-mode)))
;; minimum-file-buffer-length is not 0 because of lookahead-u8 and
;; unget-u8 and to simplify the logic for setting size and index based
;; on length. the single byte will never be used for output ports.
(define minimum-file-buffer-length 1)
(define bytevector-buffer-length 128)
(define string-buffer-length 16)
(define buffered-transcoded-port-buffer-length 1024)
(define unbuffered-transcoded-port-buffer-length 1)
(define codec-buffer-length 1024)
(define check-option ; for Chez Scheme list-based file open options
(lambda (who x y)
(when (and x (not (eq? x y)))
($oops who "incompatible options ~s and ~s" x y))))
;; Foreign calls to file system
;; use critical-section to increment/decrement disable count.
;; once we arrive in C code (e.g., bytevector-write) allow deactivation if
;; disable-count == 1. this makes our port operations multitasking
;; safe (within a single posix thread if threaded).
(define $open-input-fd
(foreign-procedure "(cs)new_open_input_fd"
(string boolean) scheme-object))
(define $open-output-fd
(foreign-procedure "(cs)new_open_output_fd"
(string int
boolean boolean boolean
boolean boolean boolean boolean)
scheme-object))
(define $open-input/output-fd
(foreign-procedure "(cs)new_open_input_output_fd"
(string int
boolean boolean boolean
boolean boolean boolean boolean)
scheme-object))
(define $close-fd
(foreign-procedure "(cs)close_fd"
(scheme-object boolean) scheme-object))
(define $bytevector-read
(foreign-procedure "(cs)bytevector_read"
(scheme-object scheme-object iptr iptr boolean) scheme-object))
(define $bytevector-read-nb
(foreign-procedure "(cs)bytevector_read_nb"
(scheme-object scheme-object iptr iptr boolean) scheme-object))
(define $bytevector-write
(foreign-procedure "(cs)bytevector_write"
(scheme-object scheme-object iptr iptr boolean) scheme-object))
(define $put-byte
(foreign-procedure "(cs)put_byte"
(scheme-object int boolean) scheme-object))
(define $set-fd-pos
(foreign-procedure "(cs)set_fd_pos"
(scheme-object scheme-object boolean) scheme-object))
(define $get-fd-pos
(foreign-procedure "(cs)get_fd_pos"
(scheme-object boolean) scheme-object))
(define $get-fd-nonblocking
(foreign-procedure "(cs)get_fd_non_blocking"
(scheme-object boolean) scheme-object))
(define $set-fd-nonblocking
(foreign-procedure "(cs)set_fd_non_blocking"
(scheme-object boolean boolean) scheme-object))
(define $get-fd-length
(foreign-procedure "(cs)get_fd_length"
(scheme-object boolean) scheme-object))
(define $set-fd-length
(foreign-procedure "(cs)set_fd_length"
(scheme-object scheme-object boolean) scheme-object))
(define $fd-regular?
(foreign-procedure "(cs)fd_regularp" (int) boolean))
(define $compress-input-fd
(foreign-procedure "(cs)compress_input_fd" (int integer-64) scheme-object))
(define $compress-output-fd
(foreign-procedure "(cs)compress_output_fd" (int) scheme-object))
(module (clear-open-files register-open-file registered-open-file? unregister-open-file)
(define open-files #f)
(define file-guardian)
(define clear-open-files
; called from single-threaded $scheme-init
(lambda ()
(set! open-files (make-weak-eq-hashtable))
(set! file-guardian (make-guardian))))
; should register only ports with known system handlers/transcoders
; we don't want to get into arbitrary user code when automatically
; closing. when files are closed, we close text ports first, then
; binary ports, so it won't generally work to register a text port that
; depends on another text port being open or a binary port that
; depends on another binary port being open.
(define register-open-file
(lambda (p)
(when open-files
(with-tc-mutex
(eq-hashtable-set! open-files p #t)
(file-guardian p)))))
(define registered-open-file?
(lambda (p)
(and open-files
(with-tc-mutex
(eq-hashtable-contains? open-files p)))))
(define unregister-open-file
(lambda (p)
(when open-files
(with-tc-mutex
(eq-hashtable-delete! open-files p)))))
(define silent-close
(lambda (pvec)
; do textual ports first, since they may encapsulate a binary port
(vector-for-each
(lambda (x)
(when (textual-port? x)
(guard (c [#t (void)]) (close-port x))))
pvec)
; now do binary ports
(vector-for-each
(lambda (x)
(when (binary-port? x)
(guard (c [#t (void)]) (close-port x))))
pvec)))
(set! $close-resurrected-files
; called from single-threaded docollect
(lambda ()
(when open-files
(silent-close
(let f ([i 0])
(let ([p (file-guardian)])
(if p
(let ([v (f (fx+ i 1))]) (vector-set! v i p) v)
(make-vector i))))))))
(set! $close-files
; called from Sscheme_deinit
(lambda ()
(with-tc-mutex
; don't attempt to close ports if other threads are still running, since the other threads might be
; using one or more of the ports up to the bitter end, and port operations are not thread-safe when
; two threads operate on the same port. in particular, trying to close a compressed port here and
; in one of the other threads concurrently can result in a double free in gzclose.
(when (and open-files (if-feature pthreads (= (length ($thread-list)) 1) #t))
(silent-close (hashtable-keys open-files)))))))
;; Helpers for binary-file-ports
(define (extract-permission-mask options)
(fxlogor
(if (enum-set-subset? (file-options perm-set-user-id) options) #o4000 0)
(if (enum-set-subset? (file-options perm-set-group-id) options) #o2000 0)
(if (enum-set-subset? (file-options perm-sticky) options) #o1000 0)
(if (enum-set-subset? (file-options perm-no-user-read) options) 0 #o400)
(if (enum-set-subset? (file-options perm-no-user-write) options) 0 #o200)
(if (enum-set-subset? (file-options perm-user-execute) options) #o100 0)
(if (enum-set-subset? (file-options perm-no-group-read) options) 0 #o40)
(if (enum-set-subset? (file-options perm-no-group-write) options) 0 #o20)
(if (enum-set-subset? (file-options perm-group-execute) options) #o10 0)
(if (enum-set-subset? (file-options perm-no-other-read) options) 0 #o4)
(if (enum-set-subset? (file-options perm-no-other-write) options) 0 #o2)
(if (enum-set-subset? (file-options perm-other-execute) options) #o1 0)))
(define-syntax do-read
(syntax-rules ()
[(_ read p_)
(let ([p p_])
(do-read read p
(binary-port-input-buffer p)
0 (bytevector-length (binary-port-input-buffer p))))]
[(_ read p_ buffer start count)
(let ([p p_])
(read ($port-info p) buffer start count (port-gz-mode p)))]))
(define-syntax bytevector-read
(syntax-rules ()
[(_ args ...) (do-read $bytevector-read args ...)]))
(define-syntax bytevector-read-nb
(syntax-rules ()
[(_ args ...) (do-read $bytevector-read-nb args ...)]))
(define bytevector-write
(lambda (who p buffer start count)
(let ([n ($bytevector-write ($port-info p) buffer start count (port-gz-mode p))])
(unless (fixnum? n) (write-oops who p n))
n)))
(define bytevector-flush
(lambda (who p buffer start count)
(let ([fd ($port-info p)] [gz (port-gz-mode p)])
(let loop ([start start] [count count])
(unless (eq? 0 count)
(let ([n ($bytevector-write fd buffer start count gz)])
(unless (fixnum? n) (write-oops who p n))
(loop (fx+ start n) (fx- count n))))))))
(define binary-file-port-flush
(lambda (who p)
(bytevector-flush who p (binary-port-output-buffer p) 0
(binary-port-output-index p))
(set-binary-port-output-index! p 0)))
(define binary-file-port-ready?
(lambda (who p)
(or (not (port-input-empty? p))
(port-flag-eof-set? p)
(let ([n (bytevector-read-nb p)])
(cond
[(fixnum? n) (set-binary-port-input-size! p n) (not (eq? n 0))]
[(eof-object? n) (set-port-eof! p #t) #t]
[(equal? n "interrupt") 'interrupt]
[else (read-oops who p n)])))))
(define binary-file-port-lookahead
(lambda (who p)
(cond
[(not (port-input-empty? p))
(bytevector-u8-ref (binary-port-input-buffer p)
(binary-port-input-index p))]
[(port-flag-eof-set? p) (eof-object)]
[else (let loop ()
(let ([n (bytevector-read p)])
(cond
[(eq? 0 n) (loop)]
[(fixnum? n)
(set-binary-port-input-size! p n)
(bytevector-u8-ref (binary-port-input-buffer p) 0)]
[(eof-object? n) (set-port-eof! p #t) n]
[(equal? n "interrupt") 'interrupt]
[else (read-oops who p n)])))])))
(define binary-file-port-unget
(lambda (who p x)
(when (port-flag-eof-set? p) (unget-error who p x))
(if (eof-object? x)
(let ()
(unless (port-input-empty? p) (unget-error who p x))
(set-port-eof! p #t))
(let ([index (binary-port-input-index p)])
(when (eq? 0 index) (unget-error who p x))
(set-binary-port-input-index! p (fx1- index))))))
(define binary-file-port-get
(lambda (who p)
(cond
[(not (port-input-empty? p))
(let ([index (binary-port-input-index p)])
(set-binary-port-input-index! p (fx1+ index))
(bytevector-u8-ref (binary-port-input-buffer p) index))]
[(port-flag-eof-set? p) (set-port-eof! p #f) (eof-object)]
[else (let loop ()
(let ([n (bytevector-read p)])
(cond
[(eq? 0 n) (loop)]
[(fixnum? n)
(set-binary-port-input-size! p n)
(set-binary-port-input-index! p 1)
(bytevector-u8-ref (binary-port-input-buffer p) 0)]
[(eof-object? n) n]
[(equal? n "interrupt") 'interrupt]
[else (read-oops who p n)])))])))
(define binary-file-port-get-some
(lambda (who p bv start count)
(let ([port-count (binary-port-input-count p)])
(cond
[(not (eq? 0 port-count))
(let ([count (fxmin count port-count)]
[index (binary-port-input-index p)])
(bytevector-copy! (binary-port-input-buffer p) index bv start count)
(set-binary-port-input-index! p (fx+ index count))
count)]
[(port-flag-eof-set? p) (set-port-eof! p #f) (eof-object)]
[(and (fx<= count max-get-copy) (fx<= count (bytevector-length (binary-port-input-buffer p))))
(let ([n (bytevector-read p)])
(cond
[(fixnum? n)
(let ([count (fxmin n count)])
(set-binary-port-input-size! p n)
(set-binary-port-input-index! p count)
(bytevector-copy! (binary-port-input-buffer p) 0 bv start count)
count)]
[(eof-object? n) n]
[(equal? n "interrupt") 'interrupt]
[else (read-oops who p n)]))]
[else (let ([n (bytevector-read p bv start count)])
(cond
[(fixnum? n) n]
[(eof-object? n) n]
[(equal? n "interrupt") 'interrupt]
[else (read-oops who p n)]))]))))
(define binary-file-port-clear-input
(lambda (who p)
(set-binary-port-input-size! p 0)))
(define binary-file-port-put
(lambda (who p x)
(let ([index (binary-port-output-index p)]
[buffer (binary-port-output-buffer p)])
(cond
[(not (port-output-full? p))
(bytevector-u8-set! buffer index x)
(set-binary-port-output-index! p (fx1+ index))]
[(fx= index 0) ; since full, => size is 0 => unbuffered
(let loop ()
(let ([n ($put-byte ($port-info p) x (port-gz-mode p))])
(unless (fixnum? n) (write-oops who p n))
(when (fx= n 0) (loop))))]
[else
(bytevector-u8-set! buffer index x)
(bytevector-flush who p buffer 0 (fx1+ index))
(set-binary-port-output-index! p 0)]))))
;; The following diagram shows the control flow of put-some.
;; It is complicated because it must handle nonblocking ports
;; while also trying to minimize the number of operating system calls and
;; being smart about when to buffer.
;;
;; Arrows marked with "@" are guarded with a try-fill that
;; will try to exit the function early by copying the new bytevector
;; into the old bytevector. Arrows marked with "@@" are the same
;; but in future versions might be willing to partially copy
;; the old buffer where as the "@" lines will only copy if
;; the entire new data fits in the old buffer.
;;
;; old is the port's buffer
;; new is the byte vector being passed in
;;
;; len(x)=0 tests whether x is empty and returns #t or #f
;; write(x) writes the old buffer to the operating system and
;; returns either ALL if all data was written or PARTIAL if
;; one part of the data was written
;; shift(old) bytevector copies to the front of old
;; the part of old that wasn't written
#|
--@-> len(old)=0 --(#f)--> write(old) --(PARTIAL)--> shift(old) --@@--> DONE
| |
| |
(#t) <---@---(ALL)---+
|
V
len(new)=0 --(#f)--> write(new) --(PARTIAL)-----------------@@--> DONE
| |
| |
(#t) (ALL)
| |
V V
DONE DONE
|#
(define binary-file-port-put-some
(lambda (who p bv start count)
;; from-start: where to fill from
;; from-count: how much to fill from (i.e. how much we want to put)
;; to-start: where to fill to
;; to-count: how much to fill to (i.e. how much room we have)
;; body: what to do if not filling
(define-syntax try-fill
(syntax-rules ()
[(_ from-start from-count to-start to-count body)
(if (and (fx<= from-count max-put-copy)
(fx<= from-count to-count))
(begin
(bytevector-copy! bv from-start
(binary-port-output-buffer p) to-start
from-count)
(set-binary-port-output-index! p (fx+ to-start from-count))
(fx+ (fx- from-start start) from-count))
body)]))
;; buffer: what to write from
;; start: where to write from
;; count: how much to write from
;; (n): var to bind to how many written
;; zero: what to do if count is zero
;; normal: what to do if all count written
;; interrupted: what to do not all count written
(define-syntax try-write
(syntax-rules ()
[(_ buffer start count (n) zero normal partial)
(if (eq? 0 count)
zero
(let ([n (bytevector-write who p buffer start count)])
(if (eq? n count)
normal
partial)))]))
;; On entry: old buffer has been completely written
;; and we need to write the new buffer
(define (write-new)
(try-write bv start count (n) 0 count
(try-fill (fx+ start n) (fx- count n) 0 (binary-port-output-size p) n)))
(let ([port-index (binary-port-output-index p)]
[port-count (binary-port-output-count p)]
[port-size (binary-port-output-size p)]
[port-buffer (binary-port-output-buffer p)])
(try-fill start count port-index port-count
(try-write port-buffer 0 port-index (n)
(write-new)
(try-fill start count 0 port-size
(begin
(set-binary-port-output-index! p 0) ;; may be reset by try-fill
(write-new)))
(let ([new-index (fx- port-index n)])
(bytevector-copy! port-buffer n port-buffer 0 new-index)
(set-binary-port-output-index! p new-index)
(try-fill start count new-index (fx- port-size new-index) 0)))))))
(define binary-file-port-clear-output
(lambda (who p)
(set-binary-port-output-index! p 0)))
(define binary-file-port-close-port
(lambda (who p)
(when (input-port? p)
(set-port-eof! p #f)
(set-binary-port-input-size! p 0))
(when (output-port? p) (set-binary-port-output-size! p 0))
(unregister-open-file p)
; mark port closed before closing fd. if an interrupt occurs, we'd prefer
; that the fd's resources never be freed than to have an open port floating
; around with fd resources that have already been freed.
(mark-port-closed! p)
(let ([msg ($close-fd ($port-info p) (port-gz-mode p))])
(unless (eq? #t msg) (port-oops who p msg)))))
(define-syntax binary-file-port-port-position
(syntax-rules ()
[(_ mode who ?p)
(member (datum mode) '(in out in/out))
(let ([p ?p])
(let ([n ($get-fd-pos ($port-info p) (port-gz-mode p))])
(unless (or (fixnum? n) (bignum? n)) (port-oops who p n))
(- (+ n (if (eq? 'mode 'in) 0 (binary-port-output-index p)))
(if (eq? 'mode 'out) 0 (binary-port-input-count p)))))]))
(define binary-file-port-set-port-position!
(lambda (who p x)
(unless (and (integer? x) (exact? x) (<= 0 x (- (expt 2 63) 1)))
($oops who "~s is not a valid position" x))
(let ([n ($set-fd-pos ($port-info p) x (port-gz-mode p))])
(unless (eq? n #t) (position-oops who p x n)))))
(define binary-file-port-port-nonblocking?
(lambda (who p)
(let ([n ($get-fd-nonblocking ($port-info p) (port-gz-mode p))])
(unless (boolean? n) (port-oops who p n))
n)))
(define binary-file-port-set-port-nonblocking!
(lambda (who p x)
(let ([n ($set-fd-nonblocking ($port-info p) x (port-gz-mode p))])
(unless (eq? n #t) (port-oops who p n)))))
(define binary-file-port-port-length
(lambda (who p)
(let ([n ($get-fd-length ($port-info p) (port-gz-mode p))])
(unless (or (fixnum? n) (bignum? n)) (port-oops who p n))
n)))
(define binary-file-port-set-port-length!
(lambda (who p x)
(unless (and (integer? x) (exact? x) (<= 0 x (- (expt 2 63) 1)))
($oops who "~s is not a valid length" x))
(let ([n ($set-fd-length ($port-info p) x (port-gz-mode p))])
(unless (eq? n #t) (port-oops who p n)))))
;; Helpers for binary-custom-ports
(define (bv-read! who p read! bv start count)
(let ([n (read! bv start count)])
(unless (and (fixnum? n) (fx<= 0 n count))
($oops who "invalid result ~s from read! on ~s" n p))
n))
(define (binary-port-read! who p read!)
(let ([bv (binary-port-input-buffer p)])
(let ([n (bv-read! who p read! bv 0 (bytevector-length bv))])
(if (eq? 0 n)
(eof-object)
(begin
(set-binary-port-input-size! p n)
(bytevector-u8-ref bv 0))))))
(define bv-write! ;; loops until count written
(lambda (who p write! bv start count)
(let loop ([start start]
[count count])
(unless (eq? 0 count)
(let ([result (write! bv start count)])
(unless (and (fixnum? result) (fx<= 0 result count))
($oops who "invalid result ~s from write! on ~s" result p))
(loop (fx+ start result) (fx- count result)))))))
(define binary-custom-port-lookahead
(lambda (who p read!)
(cond
[(not (port-input-empty? p))
(bytevector-u8-ref (binary-port-input-buffer p)
(binary-port-input-index p))]
[(port-flag-eof-set? p) (eof-object)]
[else (let ([x (binary-port-read! who p read!)])
(when (eof-object? x)
(set-port-eof! p #t))
x)])))
(define binary-custom-port-unget
(lambda (who p x)
(when (port-flag-eof-set? p) (unget-error who p x))
(if (eof-object? x)
(let ()
(unless (port-input-empty? p) (unget-error who p x))
(set-port-eof! p #t))
(let ([index (binary-port-input-index p)])
(when (eq? 0 index) (unget-error who p x))
(set-binary-port-input-index! p (fx1- index))))))
(define binary-custom-port-get
(lambda (who p read!)
(cond
[(not (port-input-empty? p))
(let ([index (binary-port-input-index p)])
(set-binary-port-input-index! p (fx1+ index))
(bytevector-u8-ref (binary-port-input-buffer p) index))]
[(port-flag-eof-set? p) (set-port-eof! p #f) (eof-object)]
[else (let ([x (binary-port-read! who p read!)])
(unless (eof-object? x)
(set-binary-port-input-index! p 1))
x)])))
(define binary-custom-port-get-some
(lambda (who p read! bv start count)
(let ([port-count (binary-port-input-count p)])
(cond
[(not (eq? 0 port-count))
(let ([count (fxmin count port-count)]
[index (binary-port-input-index p)])
(bytevector-copy! (binary-port-input-buffer p) index bv start count)
(set-binary-port-input-index! p (fx+ index count))
count)]
[(port-flag-eof-set? p) (set-port-eof! p #f) (eof-object)]
[else (let ([n (bv-read! who p read! bv start count)])
(if (eq? 0 n)
(eof-object)
n))]))))
(define binary-custom-port-clear-input
(lambda (who p)
(set-binary-port-input-size! p 0)))
(define binary-custom-port-put
(lambda (who p write! x)
(let ([buffer (binary-port-output-buffer p)]
[index (binary-port-output-index p)])
(bytevector-u8-set! buffer index x)
(let ([new-index (fx1+ index)])
(if (port-output-full? p)
(begin
(bv-write! who p write! buffer 0 new-index)
(set-binary-port-output-index! p 0))
(set-binary-port-output-index! p new-index))))))
(define binary-custom-port-put-some
(lambda (who p write! bv start count)
(if (and (fx<= count max-put-copy) (fx<= count (binary-port-output-count p)))
(begin
(let ([index (binary-port-output-index p)])
(bytevector-copy! bv start
(binary-port-output-buffer p) index
count)
(set-binary-port-output-index! p (fx+ index count))
count))
(begin
(bv-write! who p write! (binary-port-output-buffer p)
0 (binary-port-output-index p))
(bv-write! who p write! bv start count)
(set-binary-port-output-index! p 0)
count))))
(define-syntax binary-custom-port-flush
(syntax-rules ()
[(_ who p_ write!)
(let ([p p_])
(bv-write! who p write! (binary-port-output-buffer p)
0 (binary-port-output-index p))
(set-binary-port-output-index! p 0))]))
(define binary-custom-port-clear-output
(lambda (who p)
(set-binary-port-output-index! p 0)))
(define binary-custom-port-close-port
(lambda (who p close)
(when close (close))
(mark-port-closed! p)
(when (input-port? p)
(set-port-eof! p #f)
(set-binary-port-input-size! p 0))
(when (output-port? p) (set-binary-port-output-size! p 0))))
(define-syntax binary-custom-port-port-position
(syntax-rules ()
[(_ mode who ?p get-position)
(member (datum mode) '(in out in/out))
(let ([p ?p])
(let ([n (get-position)])
(unless (or (and (fixnum? n) (fx>= n 0)) (and (bignum? n) (>= n 0)))
($oops who "invalid result ~s from get-position on ~s" n p))
(- (+ n (if (eq? 'mode 'in) 0 (binary-port-output-index p)))
(if (eq? 'mode 'out) 0 (binary-port-input-count p)))))]))
;; Helpers for textual-custom-ports
(define (str-read! who p read! str start count)
(let ([n (read! str start count)])
(unless (and (fixnum? n) (fx<= 0 n count))
($oops who "invalid result ~s from read! on ~s" n p))
n))
(define (textual-port-read! who p read!)
(let ([str (textual-port-input-buffer p)])
(let ([n (str-read! who p read! str 0 (string-length str))])
(if (fx= n 0)
(eof-object)
(begin
(set-textual-port-input-size! p n)
(string-ref str 0))))))
(define str-write! ;; loops until count written
(lambda (who p write! str start count)
(let loop ([start start] [count count])
(unless (fx= count 0)
(let ([result (write! str start count)])
(unless (and (fixnum? result) (fx<= 0 result count))
($oops who "invalid result ~s from write! on ~s" result p))
(loop (fx+ start result) (fx- count result)))))))
(define textual-custom-port-lookahead
(lambda (who p read!)
(cond
[(not (port-input-empty? p))
(string-ref
(textual-port-input-buffer p)
(textual-port-input-index p))]
[(port-flag-eof-set? p) (eof-object)]
[else
(let ([x (textual-port-read! who p read!)])
(when (eof-object? x) (set-port-eof! p #t))
x)])))
(define textual-custom-port-unget
(lambda (who p x)
(when (port-flag-eof-set? p) (unget-error who p x))
(if (eof-object? x)
(let ()
(unless (port-input-empty? p) (unget-error who p x))
(set-port-eof! p #t))
(let ([index (textual-port-input-index p)])
(when (eq? 0 index) (unget-error who p x))
(set-textual-port-input-index! p (fx1- index))))))
(define textual-custom-port-get
(lambda (who p read!)
(cond
[(not (port-input-empty? p))
(let ([index (textual-port-input-index p)])
(set-textual-port-input-index! p (fx1+ index))
(string-ref (textual-port-input-buffer p) index))]
[(port-flag-eof-set? p) (set-port-eof! p #f) (eof-object)]
[else (let ([x (textual-port-read! who p read!)])
(unless (eof-object? x)
(set-textual-port-input-index! p 1))
x)])))
(define textual-custom-port-get-some
(lambda (who p read! str start count)
(let ([port-count (textual-port-input-count p)])
(cond
[(not (fx= port-count 0))
(let ([count (fxmin count port-count)]
[index (textual-port-input-index p)])
(string-copy! (textual-port-input-buffer p) index str start count)
(set-textual-port-input-index! p (fx+ index count))
count)]
[(port-flag-eof-set? p) (set-port-eof! p #f) (eof-object)]
[else (let ([n (str-read! who p read! str start count)])
(if (eq? 0 n)
(eof-object)
n))]))))
(define textual-custom-port-clear-input
(lambda (who p)
(set-textual-port-input-size! p 0)))
(define textual-custom-port-put
(lambda (who p write! x)
(let ([buffer (textual-port-output-buffer p)]
[index (textual-port-output-index p)])
(string-set! buffer index x)
(let ([new-index (fx1+ index)])
(if (port-output-full? p)
(begin
(str-write! who p write! buffer 0 new-index)
(set-port-bol! p (eol-char? (string-ref buffer index)))
(set-textual-port-output-index! p 0))
(set-textual-port-output-index! p new-index))))))
(define textual-custom-port-put-some
(lambda (who p write! str start count)
(if (and (fx<= count max-put-copy) (fx<= count (textual-port-output-count p)))
(begin
(let ([index (textual-port-output-index p)])
(string-copy! str start
(textual-port-output-buffer p) index
count)
(set-textual-port-output-index! p (fx+ index count))
count))
(begin
(str-write! who p write! (textual-port-output-buffer p)
0 (textual-port-output-index p))
(str-write! who p write! str start count)
(set-textual-port-output-index! p 0)
(set-port-bol! p (eol-char? (string-ref str (fx- (fx+ start count) 1))))
count))))
(define textual-custom-port-flush
(lambda (who p write!)
(let ([n (textual-port-output-index p)])
(unless (fx= n 0)
(let ([buffer (textual-port-output-buffer p)])
(str-write! who p write! buffer 0 n)
(set-port-bol! p (eol-char? (string-ref buffer (fx- n 1))))
(set-textual-port-output-index! p 0))))))
(define textual-custom-port-clear-output
(lambda (who p)
(set-textual-port-output-index! p 0)))
(define textual-custom-port-close-port
(lambda (who p close)
(when close (close))
(mark-port-closed! p)
(when (input-port? p)
(set-port-eof! p #f)
(set-textual-port-input-size! p 0))
(when (output-port? p) (set-textual-port-output-size! p 0))))
(define-syntax check-interrupt
(syntax-rules ()
[(_ e)
(let loop ()
(let ([x e])
(if (eq? x 'interrupt)
(begin ($event) (loop))
x)))]))
(module (open-binary-fd-input-port)
;; NOTE: port-info stores the file descriptor number or gzFile object
(define (make-binary-file-input-handler regular?)
(make-port-handler
[ready?
(lambda (who p)
(check-interrupt
(critical-section
(assert-not-closed who p)
(binary-file-port-ready? who p))))]
[lookahead
(lambda (who p)
(check-interrupt
(critical-section
(assert-not-closed who p)
(binary-file-port-lookahead who p))))]
[unget
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-unget who p x)))]
[get
(lambda (who p)
(check-interrupt
(critical-section
(assert-not-closed who p)
(binary-file-port-get who p))))]
[get-some
(lambda (who p bv start count)
(check-interrupt
(critical-section
(assert-not-closed who p)
(binary-file-port-get-some who p bv start count))))]
[clear-input
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-clear-input who p)))]
[put #f]
[put-some #f]
[flush #f]
[clear-output #f]
[close-port
(lambda (who p)
(critical-section
(unless (port-closed? p)
(binary-file-port-close-port who p))))]
[port-position
(and regular?
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-position in who p))))]
[set-port-position!
(and regular?
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-set-port-position! who p x)
(set-binary-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f))))]
[port-length
(and regular?
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-length who p))))]
[set-port-length! #f]
[port-nonblocking?
(if-feature windows #f
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-nonblocking? who p))))]
[set-port-nonblocking!
(if-feature windows #f
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-set-port-nonblocking! who p x))))]))
(define open-binary-fd-input-port
(lambda (who name fd regular? mode gzflag)
(let ([buffer-length (if (eq? mode (buffer-mode none))
minimum-file-buffer-length
(file-buffer-size))])
(let ([p ($make-binary-input-port
name ;; name
(make-binary-file-input-handler regular?) ;; handler
(make-bytevector buffer-length) ;; buffer
fd)]) ;; info
(if (eq? mode (buffer-mode block))
($set-port-flags! p (constant port-flag-block-buffered))
(when (eq? mode (buffer-mode line))
($set-port-flags! p (constant port-flag-line-buffered))))
($set-port-flags! p (constant port-flag-file))
(when gzflag
($set-port-flags! p (constant port-flag-compressed)))
;; size is set by $make-binary-input-port, but
;; we want it to trip the handler the first time so
;; re-set the size to zero
(set-binary-port-input-size! p 0)
(register-open-file p)
p)))))
(module (open-binary-fd-output-port)
;; NOTE: output-size is one less than actual buffer size so
;; we always have a place to put data before calling write
(define (make-binary-file-output-handler regular?)
(make-port-handler
[ready? #f]
[lookahead #f]
[unget #f]
[get #f]
[get-some #f]
[clear-input #f]
[put
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-put who p x)))]
[put-some
(lambda (who p bv start count)
(critical-section
(assert-not-closed who p)
(binary-file-port-put-some who p bv start count)))]
[flush
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-flush who p)))]
[clear-output
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-clear-output who p)))]
[close-port
(lambda (who p)
(critical-section
(unless (port-closed? p)
(binary-file-port-flush who p)
(binary-file-port-close-port who p))))]
[port-position
(and regular?
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-position out who p))))]
[set-port-position!
(and regular?
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-flush who p)
(binary-file-port-set-port-position! who p x))))]
[port-length
(and regular?
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-flush who p)
(binary-file-port-port-length who p))))]
[set-port-length!
(and regular?
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-flush who p)
(binary-file-port-set-port-length! who p x))))]
[port-nonblocking?
(if-feature windows #f
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-nonblocking? who p))))]
[set-port-nonblocking!
(if-feature windows #f
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-set-port-nonblocking! who p x))))]))
(define open-binary-fd-output-port
(lambda (who name fd regular? b-mode lock compressed)
(let ([buffer-length (if (eq? b-mode (buffer-mode none))
minimum-file-buffer-length
(file-buffer-size))])
(let ([p ($make-binary-output-port
name ;; name
(make-binary-file-output-handler regular?) ;; handler
(make-bytevector buffer-length) ;; buffer
fd)]) ;; info
(if (eq? b-mode (buffer-mode block))
($set-port-flags! p (constant port-flag-block-buffered))
(when (eq? b-mode (buffer-mode line))
($set-port-flags! p (constant port-flag-line-buffered))))
($set-port-flags! p (constant port-flag-file))
(when compressed
($set-port-flags! p (constant port-flag-compressed)))
(when lock
($set-port-flags! p (constant port-flag-exclusive)))
(set-binary-port-output-size! p (fx1- buffer-length)) ;; leave room for put to work
(register-open-file p)
p)))))
(module (open-binary-fd-input/output-port)
;; Two modes: ready-for-input and ready-for-output
;;
;; ready-for-input: output-size == 0
;; ready-for-output: output-size == length-1 and input-size == 0
;;
;; unbuffered port (ports with length 1 buffers) may be both
;; ready-for-input and ready-for-output simultaneously,
;; but it is never the case that both
;; output-size != 0 and input-size != 0
;;
;; for our purposes having the eof flag set is the same as input-size != 0
(define-syntax make-ready-for-input
(syntax-rules ()
[(_ who p_)
(let ([p p_])
(unless (eq? 0 (binary-port-output-size p))
(binary-file-port-flush who p)
;; don't set input-size; it is set only after a read
(set-binary-port-output-size! p 0)))]))
(module ((make-ready-for-output $make-ready-for-output))
(define $make-ready-for-output
(lambda (who p)
(unless (eq? (binary-port-input-size p) 0)
(unless (port-input-empty? p)
(binary-file-port-set-port-position! who p
(binary-file-port-port-position in/out who p)))
(set-binary-port-input-size! p 0))
(set-port-eof! p #f)
(set-binary-port-output-size! p
(fx1- (bytevector-length (binary-port-output-buffer p))))))
(define-syntax make-ready-for-output
(syntax-rules ()
[(_ ?who ?p)
(let ([p ?p])
(when (eq? (binary-port-output-size p) 0)
($make-ready-for-output ?who p)))])))
(define (make-binary-file-input/output-handler regular?)
(make-port-handler
[ready?
(lambda (who p)
(check-interrupt
(critical-section
(assert-not-closed who p)
(make-ready-for-input who p)
(binary-file-port-ready? who p))))]
[lookahead
(lambda (who p)
(check-interrupt
(critical-section
(assert-not-closed who p)
(make-ready-for-input who p)
(binary-file-port-lookahead who p))))]
[unget
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(make-ready-for-input who p)
(binary-file-port-unget who p x)))]
[get
(lambda (who p)
(check-interrupt
(critical-section
(assert-not-closed who p)
(make-ready-for-input who p)
(binary-file-port-get who p))))]
[get-some
(lambda (who p bv start count)
(check-interrupt
(critical-section
(assert-not-closed who p)
(make-ready-for-input who p)
(binary-file-port-get-some who p bv start count))))]
[clear-input
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-clear-input who p)))]
[put
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(make-ready-for-output who p)
(binary-file-port-put who p x)))]
[put-some
(lambda (who p bv start count)
(critical-section
(assert-not-closed who p)
(make-ready-for-output who p)
(binary-file-port-put-some who p bv start count)))]
[flush
(lambda (who p)
(critical-section
(assert-not-closed who p)
(make-ready-for-output who p)
(binary-file-port-flush who p)))]
[clear-output
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-clear-output who p)))]
[close-port
(lambda (who p)
(critical-section
(unless (port-closed? p)
(binary-file-port-flush who p)
(binary-file-port-close-port who p))))]
[port-position
(and regular?
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-position in/out who p))))]
[set-port-position!
(and regular?
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-flush who p)
(binary-file-port-set-port-position! who p x)
(set-binary-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f))))]
[port-length
(and regular?
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-flush who p)
(binary-file-port-port-length who p))))]
[set-port-length!
(and regular?
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(cond
[(and (fx= (binary-port-input-size p) 0) (not (port-flag-eof-set? p)))
(binary-file-port-flush who p)
(binary-file-port-set-port-length! who p x)]
[else
(let ([pos (binary-file-port-port-position in/out who p)])
(set-binary-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f)
(binary-file-port-set-port-length! who p x)
(binary-file-port-set-port-position! who p pos))]))))]
[port-nonblocking?
(if-feature windows #f
(lambda (who p)
(critical-section
(assert-not-closed who p)
(binary-file-port-port-nonblocking? who p))))]
[set-port-nonblocking!
(if-feature windows #f
(lambda (who p x)
(critical-section
(assert-not-closed who p)
(binary-file-port-set-port-nonblocking! who p x))))]))
(define open-binary-fd-input/output-port
(lambda (who name fd regular? b-mode lock compressed)
(let ([buffer-length (if (eq? b-mode (buffer-mode none))
minimum-file-buffer-length
(file-buffer-size))])
(let ([p ($make-binary-input/output-port
name ;; name
(make-binary-file-input/output-handler regular?) ;; handler
(make-bytevector buffer-length) ;; input buffer
(make-bytevector buffer-length) ;; output buffer
fd)]) ;; info
(if (eq? b-mode (buffer-mode block))
($set-port-flags! p (constant port-flag-block-buffered))
(when (eq? b-mode (buffer-mode line))
($set-port-flags! p (constant port-flag-line-buffered))))
($set-port-flags! p (constant port-flag-file))
(when compressed
($set-port-flags! p (constant port-flag-compressed)))
(when lock
($set-port-flags! p (constant port-flag-exclusive)))
;; size is set by $make-binary-input/output-port, but
;; we want it to trip the handler the first time so
;; re-set the size to zero
(set-binary-port-input-size! p 0)
(set-binary-port-output-size! p (fx1- buffer-length)) ;; leave room for put to work
(register-open-file p)
p)))))
;;;; Public functions
;; All section numbers are from ``R6RS -- Standard Libraries''
;;;; 8.1 Condition types (in exceptions.ss)
;;;; 8.2 Port I/O: (rnrs io ports (6))
;;;; 8.2.1 Filenames
;;;; 8.2.2 File options
;; file-options in syntax.ss
(set-who! $file-options (make-enumeration (file-options-list)))
(set-who! $make-file-options (enum-set-constructor $file-options))
;;;; 8.2.3 Buffer modes
;; buffer-mode in syntax.ss
(set-who! buffer-mode?
(lambda (mode) (and (memq mode '(none line block)) #t)))
;;;; 8.2.4 Transcoders
(let ()
(define (encode-oops who tp c)
($oops/c who
(make-i/o-encoding-error tp c)
(parameterize ([print-unicode #f])
(let* ([tx (codec-info-tx ($port-info tp))]
[name (codec-name ($transcoder-codec tx))])
(if (and (eqv? c #\newline) (not (memq ($transcoder-eol-style tx) '(none lf))))
(format "~a codec cannot encode ~s with eol-style ~s"
name c ($transcoder-eol-style tx))
(format "~a codec cannot encode ~s" name c))))))
(define (decode-oops who tp msg . args)
(apply $oops/c who
(make-i/o-decoding-error tp)
msg args))
(define (flush-buffer who bp bv i k)
(if (fx= k 0)
0
(let ([n (call-port-handler put-some who bp bv i k)])
(if (fx= n 0)
(begin
(unless (fx= i 0) (bytevector-copy! bv i bv 0 k))
k)
(flush-buffer who bp bv (fx+ i n) (fx- k n))))))
(define get-some-maybe-nb
; get some from binary port bp. if ifready? is true, don't block if port
; isn't ready, even if port has not been set nonblocking
(lambda (who bp bv start ifready?)
(let ([h ($port-handler bp)])
; port-handler-ready? may raise an exception, but that's okay because ifready?
; is true only if this is called from transcoded-port's port-handler-ready?.
(if (or (not ifready?) ((port-handler-ready? h) who bp))
((port-handler-get-some h) who bp bv start (fx- codec-buffer-length start))
0))))
(let ()
(define latin-1-decode
(let ()
(define (return ans i iend cr? bytes info)
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info bytes)
(codec-info-icr-set! info cr?)
ans)
(lambda (who tp str start count ifready?)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[ioffsets (and (eq? str (textual-port-input-buffer tp)) (codec-info-ioffsets info))]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(let loop ([j start]
[i (codec-info-next info)]
[iend (codec-info-iend info)]
[cr? (codec-info-icr info)]
[bytes 0])
(cond
[(fx= j jend) (return count i iend cr? bytes info)]
[(fx= i iend)
(if (fx= j start)
(let ([n (get-some-maybe-nb who bp bv 0 ifready?)])
(cond
[(eof-object? n) (return #!eof i iend #f bytes info)]
[(fx= n 0) (return 0 i iend cr? bytes info)]
[else (loop j 0 n cr? bytes)]))
; don't try to read in this case to avoid dealing with eof
(return (fx- j start) i iend cr? bytes info))]
[else
(let ([b (bytevector-u8-ref bv i)])
(cond
[(fx= b #x0d)
(cond
[(eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(string-set! str j #\return)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend cr? (fx+ bytes 1))]
[else
(string-set! str j #\newline)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend #t (fx+ bytes 1))])]
[(fx= b #x0a)
(cond
[cr? (loop j (fx+ i 1) iend #f (fx+ bytes 1))]
[else
(string-set! str j #\newline)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend cr? (fx+ bytes 1))])]
[(fx= b #x85) ; NEL
(cond
[cr? (loop j (fx+ i 1) iend #f (fx+ bytes 1))]
[else
(string-set! str j
(if (eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(integer->char #x85)
#\newline))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend cr? (fx+ bytes 1))])]
[else
(string-set! str j (integer->char b))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend #f (fx+ bytes 1))]))])))))))
(define latin-1-encode
(let ()
(define (return ans o info)
(codec-info-next-set! info o)
ans)
(lambda (who tp str start count)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(let loop ([j start] [o (codec-info-next info)])
(cond
[(fx= j jend) (return count o info)]
[(fx= o codec-buffer-length)
(let ([o (flush-buffer who bp bv 0 o)])
(if (fx= o codec-buffer-length)
(return (fx- j start) o info)
(loop j o)))]
[else
(let ([int (char->integer (string-ref str j))])
(cond
[(fx= int #x0a)
(let ([eol-style ($transcoder-eol-style (codec-info-tx info))])
(case eol-style
[(lf none)
(bytevector-u8-set! bv o #x0a)
(loop (fx+ j 1) (fx+ o 1))]
[(cr)
(bytevector-u8-set! bv o #x0d)
(loop (fx+ j 1) (fx+ o 1))]
[(nel)
(bytevector-u8-set! bv o #x85)
(loop (fx+ j 1) (fx+ o 1))]
[(crlf crnel)
(let f ([o o])
(if (fx< o (fx- codec-buffer-length 1))
(begin
(bytevector-u8-set! bv o #x0d)
(bytevector-u8-set! bv (fx+ o 1) (if (eq? eol-style 'crlf) #x0a #x85))
(loop (fx+ j 1) (fx+ o 2)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(f new-o)))))]
[(ls)
(let ([error-mode ($transcoder-error-handling-mode (codec-info-tx info))])
(case error-mode
[(ignore) (loop (fx+ j 1) o)]
[(replace)
(bytevector-u8-set! bv o (char->integer #\?))
(loop (fx+ j 1) (fx+ o 1))]
[(raise) (encode-oops who tp #\newline)]
[else ($oops who "unknown error handling mode ~s" error-mode)]))]
[else ($oops who "unrecognized eol style ~s" eol-style)]))]
[(fx<= int 255)
(bytevector-u8-set! bv o int)
(loop (fx+ j 1) (fx+ o 1))]
[else
(let ([error-mode ($transcoder-error-handling-mode (codec-info-tx info))])
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) (loop (fx+ j 1) o)]
[(replace)
(bytevector-u8-set! bv o (char->integer #\?))
(loop (fx+ j 1) (fx+ o 1))]
[(raise) (encode-oops who tp (string-ref str j))]
[else ($oops who "unknown error handling mode ~s" error-mode)]))]))])))))))
(set-who! latin-1-codec
(let ()
(define codec
(make-codec
[name "latin-1"]
[make-info
(lambda (who tx bp bv)
(make-codec-info tx bp bv 0 0
(and (input-port? bp) (make-fxvector (bytevector-length bv)))
0 #f #f #f #f
latin-1-decode latin-1-encode (lambda (info) #f)))]))
(lambda () codec))))
(let ()
(define utf-8-decode
(let ()
(define (err who tp info i iend bytes b . b*)
(codec-info-bom-set! info #f)
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info (fx+ bytes 1 (length b*)))
(codec-info-icr-set! info #f)
(decode-oops who tp "invalid utf-8 encoding #x~2,'0x~{, ~a~}" b
(map (lambda (b) (if (eof-object? b) "#!eof" (format "#x~2,'0x" b))) b*)))
(define (eof-err who tp info i iend bytes)
(unless (fx= bytes 0) (codec-info-bom-set! info #f))
(codec-info-next-set! info iend)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info (fx+ bytes (fx- iend i)))
(codec-info-icr-set! info #f)
(decode-oops who tp "unexpected end-of-file reading multibyte utf-8 encoding"))
(define (return ans i iend cr? bytes info)
(unless (fx= bytes 0) (codec-info-bom-set! info #f))
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info bytes)
(codec-info-icr-set! info cr?)
ans)
(lambda (who tp str start count ifready?)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[ioffsets (and (eq? str (textual-port-input-buffer tp)) (codec-info-ioffsets info))]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(define-syntax decode-error
(syntax-rules ()
[(_ j i iend bytes b1 b2 ...)
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) j]
[(replace)
(string-set! str j #\xfffd)
(when ioffsets (fxvector-set! ioffsets j bytes))
(fx+ j 1)]
[else (err who tp info i iend bytes b1 b2 ...)])]))
(define-syntax decode-eof-error
(syntax-rules ()
[(_ j i iend bytes)
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) (return #!eof iend iend #f (fx+ bytes (fx- iend i)) info)]
[(replace)
(string-set! str j #\xfffd)
(when ioffsets (fxvector-set! ioffsets j bytes))
(return (fx- (fx+ j 1) start) iend iend #f (fx+ bytes (fx- iend i)) info)]
[else (eof-err who tp info i iend bytes)])]))
(let loop ([j start]
[i (codec-info-next info)]
[iend (codec-info-iend info)]
[cr? (codec-info-icr info)]
[bytes 0])
(cond
[(fx= j jend) (pariah (return count i iend cr? bytes info))]
[(fx= i iend)
(pariah
(if (fx= j start)
(let ([n (get-some-maybe-nb who bp bv 0 ifready?)])
(cond
[(eof-object? n) (return #!eof 0 0 #f bytes info)]
[(fx= n 0) (return 0 0 0 cr? bytes info)]
[else (loop j 0 n cr? bytes)]))
; don't try to read in this case to avoid dealing with eof
(return (fx- j start) i iend cr? bytes info)))]
[else
(let ([b1 (bytevector-u8-ref bv i)])
(cond
[(fx<= b1 #x7f) ; one-byte encoding
(cond
[(fx= b1 #x0d)
(pariah
(cond
[(eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(string-set! str j #\return)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend cr? (fx+ bytes 1))]
[else
(string-set! str j #\newline)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend #t (fx+ bytes 1))]))]
[(fx= b1 #x0a)
(pariah
(cond
[cr? (loop j (fx+ i 1) iend #f (fx+ bytes 1))]
[else
(string-set! str j #\newline)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend cr? (fx+ bytes 1))]))]
[else
(string-set! str j (integer->char b1))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 1) iend #f (fx+ bytes 1))])]
[else
(pariah
(cond
[(fx<= #xc2 b1 #xdf) ; two-byte encoding
(let f ([i i] [iend iend])
(if (fx< (fx+ i 1) iend) ; have at least two bytes?
(let ([b2 (bytevector-u8-ref bv (fx+ i 1))])
(if (fx= (fxsrl b2 6) #b10) ; second byte a continuation byte?
(let ([x (fxlogor (fxsll (fxlogand b1 #b11111) 6) (fxlogand b2 #b111111))]
[i (fx+ i 2)])
(cond
[(fx= x #x85) ; NEL
(cond
[cr? (loop j i iend #f (fx+ bytes 2))]
[else
(string-set! str j
(if (eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(integer->char #x85)
#\newline))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) i iend cr? (fx+ bytes 2))])]
[else
(string-set! str j (integer->char x))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) i iend #f (fx+ bytes 2))]))
; second byte is not a continuation byte
(let ([j (decode-error j (fx+ i 1) iend bytes b1)])
(loop j (fx+ i 1) iend #f (fx+ bytes 1)))))
; have only one byte
(begin
(bytevector-u8-set! bv 0 b1)
(let ([i 0] [iend 1])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n) (decode-eof-error j i iend bytes)]
[(fx= n 0) (return (fx- j start) i iend cr? bytes info)]
[else (f i (fx+ iend n))]))))))]
[(fx<= #xe0 b1 #xef) ; three-byte encoding
(let f ([i i] [iend iend])
(if (fx< (fx+ i 1) iend) ; have at least two bytes?
(let ([b2 (bytevector-u8-ref bv (fx+ i 1))])
(if (fx= (fxsrl b2 6) #b10) ; second byte a continuation byte?
(if (fx< (fx+ i 2) iend) ; have at least three bytes?
(let ([b3 (bytevector-u8-ref bv (fx+ i 2))])
(if (fx= (fxsrl b3 6) #b10) ; third byte a continuation byte?
(let ([x (fxlogor
(fxsll (fxlogand b1 #b1111) 12)
(fxsll (fxlogand b2 #b111111) 6)
(fxlogand b3 #b111111))]
[i (fx+ i 3)])
(cond
[(and (fx= x #xfeff) (fx= bytes 0) (codec-info-bom info))
(loop j i iend #f (fx+ bytes 3))]
[(and (fx>= x #x800) (not (fx<= #xd800 x #xdfff)))
(string-set! str j
(if (and (fx= x #x2028) ; LS
(not (eq? ($transcoder-eol-style (codec-info-tx info)) 'none)))
#\newline
(integer->char x)))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) i iend #f (fx+ bytes 3))]
[else
(let ([j (decode-error j i iend bytes b1 b2 b3)])
(loop j i iend #f (fx+ bytes 3)))]))
; third byte is not a continuation byte
(let ([j (decode-error j (fx+ i 2) iend bytes b1 b2)])
(loop j (fx+ i 2) iend #f (fx+ bytes 2)))))
; have only two bytes
(begin
(bytevector-u8-set! bv 0 b1)
(bytevector-u8-set! bv 1 b2)
(let ([i 0] [iend 2])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n) (decode-eof-error j i iend bytes)]
[(fx= n 0) (return (fx- j start) i iend cr? bytes info)]
[else (f i (fx+ iend n))])))))
; second byte is not a continuation byte
(let ([j (decode-error j (fx+ i 1) iend bytes b1)])
(loop j (fx+ i 1) iend #f (fx+ bytes 1)))))
; have only one byte
(begin
(bytevector-u8-set! bv 0 b1)
(let ([i 0] [iend 1])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n) (decode-eof-error j i iend bytes)]
[(fx= n 0) (return (fx- j start) i iend cr? bytes info)]
[else (f i (fx+ iend n))]))))))]
[(fx<= #xf0 b1 #xf4) ; four-byte encoding
(let f ([i i] [iend iend])
(if (fx< (fx+ i 1) iend) ; have at least two bytes?
(let ([b2 (bytevector-u8-ref bv (fx+ i 1))])
(if (fx= (fxsrl b2 6) #b10) ; second byte a continuation byte?
(if (fx< (fx+ i 2) iend) ; have at least three bytes?
(let ([b3 (bytevector-u8-ref bv (fx+ i 2))])
(if (fx= (fxsrl b3 6) #b10) ; third byte a continuation byte?
(if (fx< (fx+ i 3) iend) ; have at least four bytes?
(let ([b4 (bytevector-u8-ref bv (fx+ i 3))])
(if (fx= (fxsrl b4 6) #b10) ; fourth byte a continuation byte?
(let ([x (fxlogor
(fxsll (fxlogand b1 #b111) 18)
(fxsll (fxlogand b2 #b111111) 12)
(fxsll (fxlogand b3 #b111111) 6)
(fxlogand b4 #b111111))]
[i (fx+ i 4)])
(cond
[(fx<= #x10000 x #x10ffff)
(string-set! str j (integer->char x))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) i iend #f (fx+ bytes 4))]
[else
(let ([j (decode-error j i iend bytes b1 b2 b3)])
(loop j i iend #f (fx+ bytes 3)))]))
; fourth byte is not a continuation byte
(let ([j (decode-error j (fx+ i 3) iend bytes b1 b2 b3)])
(loop j (fx+ i 3) iend #f (fx+ bytes 3)))))
; have only three bytes
(begin
(bytevector-u8-set! bv 0 b1)
(bytevector-u8-set! bv 1 b2)
(bytevector-u8-set! bv 2 b3)
(let ([i 0] [iend 3])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n) (decode-eof-error j i iend bytes)]
[(fx= n 0) (return (fx- j start) i iend cr? bytes info)]
[else (f i (fx+ iend n))])))))
; third byte is not a continuation byte
(let ([j (decode-error j (fx+ i 2) iend bytes b1 b2)])
(loop j (fx+ i 2) iend #f (fx+ bytes 2)))))
; have only two bytes
(begin
(bytevector-u8-set! bv 0 b1)
(bytevector-u8-set! bv 1 b2)
(let ([i 0] [iend 2])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n) (decode-eof-error j i iend bytes)]
[(fx= n 0) (return (fx- j start) i iend cr? bytes info)]
[else (f i (fx+ iend n))])))))
; second byte is not a continuation byte
(let ([j (decode-error j (fx+ i 1) iend bytes b1)])
(loop j (fx+ i 1) iend #f (fx+ bytes 1)))))
; have only one byte
(begin
(bytevector-u8-set! bv 0 b1)
(let ([i 0] [iend 1])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n) (decode-eof-error j i iend bytes)]
[(fx= n 0) (return (fx- j start) i iend cr? bytes info)]
[else (f i (fx+ iend n))]))))))]
[else
(let ([j (decode-error j (fx+ i 1) iend bytes b1)])
(loop j (fx+ i 1) iend #f (fx+ bytes 1)))]))]))])))))))
(define utf-8-encode
(let ()
(define (return ans o info)
(codec-info-next-set! info o)
ans)
(define (write-two-byte bv o x)
(bytevector-u8-set! bv o (fxlogor #b11000000 (fxsrl x 6)))
(bytevector-u8-set! bv (fx+ o 1) (fxlogor #b10000000 (fxlogand x #b111111))))
(define (write-three-byte bv o x)
(bytevector-u8-set! bv o (fxlogor #b11100000 (fxsrl x 12)))
(bytevector-u8-set! bv (fx+ o 1) (fxlogor #b10000000 (fxlogand (fxsrl x 6) #b111111)))
(bytevector-u8-set! bv (fx+ o 2) (fxlogor #b10000000 (fxlogand x #b111111))))
(lambda (who tp str start count)
(let ([info ($port-info tp)])
(codec-info-bom-set! info #f)
(let ([bp (codec-info-bp info)]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(let loop ([j start] [o (codec-info-next info)])
(cond
[(fx= j jend) (return count o info)]
[(fx= o codec-buffer-length)
(let ([o (flush-buffer who bp bv 0 o)])
(if (fx= o codec-buffer-length)
(return (fx- j start) o info)
(loop j o)))]
[else
(let ([x (char->integer (string-ref str j))])
(cond
[(fx= x #x0a)
(let ([eol-style ($transcoder-eol-style (codec-info-tx info))])
(case eol-style
[(lf none)
(bytevector-u8-set! bv o #x0a)
(loop (fx+ j 1) (fx+ o 1))]
[(cr)
(bytevector-u8-set! bv o #x0d)
(loop (fx+ j 1) (fx+ o 1))]
[(crlf nel)
(let f ([o o])
(if (fx< o (fx- codec-buffer-length 1))
(begin
(case eol-style
[(crlf)
(bytevector-u8-set! bv o #x0d)
(bytevector-u8-set! bv (fx+ o 1) #x0a)]
[else (write-two-byte bv o #x85)])
(loop (fx+ j 1) (fx+ o 2)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(f new-o)))))]
[(crnel ls)
(let f ([o o])
(if (fx< o (fx- codec-buffer-length 2))
(begin
(case eol-style
[(crnel)
(bytevector-u8-set! bv o #x0d)
(write-two-byte bv (fx+ o 1) #x85)]
[else (write-three-byte bv o #x2028)])
(loop (fx+ j 1) (fx+ o 3)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(f new-o)))))]
[else ($oops who "unrecognized eol style ~s" eol-style)]))]
[(fx<= x #x7f) ; one-byte encoding
(bytevector-u8-set! bv o x)
(loop (fx+ j 1) (fx+ o 1))]
[(fx<= x #x7ff) ; two-byte encoding
(let f ([o o])
(if (fx< o (fx- codec-buffer-length 1))
(begin
(write-two-byte bv o x)
(loop (fx+ j 1) (fx+ o 2)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(f new-o)))))]
[(fx<= x #xffff) ; three-byte encoding
(let f ([o o])
(if (fx< o (fx- codec-buffer-length 2))
(begin
(write-three-byte bv o x)
(loop (fx+ j 1) (fx+ o 3)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(f new-o)))))]
[else ; four-byte encoding
(let f ([o o])
(if (fx< o (fx- codec-buffer-length 3))
(begin
(bytevector-u8-set! bv o (fxlogor #b11110000 (fxsrl x 18)))
(bytevector-u8-set! bv (fx+ o 1) (fxlogor #b10000000 (fxlogand (fxsrl x 12) #b111111)))
(bytevector-u8-set! bv (fx+ o 2) (fxlogor #b10000000 (fxlogand (fxsrl x 6) #b111111)))
(bytevector-u8-set! bv (fx+ o 3) (fxlogor #b10000000 (fxlogand x #b111111)))
(loop (fx+ j 1) (fx+ o 4)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(f new-o)))))]))])))))))
(set-who! utf-8-codec
(let ()
(define codec
(make-codec
[name "utf-8"]
[make-info
(lambda (who tx bp bv)
(make-codec-info tx bp bv 0 0
(and (input-port? bp) (make-fxvector (bytevector-length bv)))
0 #f #t #f #f
utf-8-decode utf-8-encode (lambda (info) #f)))]))
(lambda () codec))))
(let ()
(define utf-16-decode
(let ()
(define (err who tp info i iend bytes b . b*)
(codec-info-bom-set! info #f)
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info (fx+ bytes 1 (length b*)))
(codec-info-icr-set! info #f)
(decode-oops who tp "invalid utf-16 encoding #x~2,'0x~{, ~a~}" b
(map (lambda (b) (if (eof-object? b) "#!eof" (format "#x~2,'0x" b))) b*)))
(define (eof-err who tp info i iend bytes)
(unless (fx= bytes 0) (codec-info-bom-set! info #f))
(codec-info-next-set! info iend)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info (fx+ bytes (fx- iend i)))
(codec-info-icr-set! info #f)
(decode-oops who tp "unexpected end-of-file reading two-word utf-16 encoding"))
(define (return ans i iend cr? bytes info)
(unless (fx= bytes 0) (codec-info-bom-set! info #f))
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
(codec-info-ibytes-set! info bytes)
(codec-info-icr-set! info cr?)
ans)
(lambda (who tp str start count ifready?)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[ioffsets (and (eq? str (textual-port-input-buffer tp)) (codec-info-ioffsets info))]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(define-syntax decode-error
(syntax-rules ()
[(_ j i iend bytes b1 b2 ...)
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) j]
[(replace)
(string-set! str j #\xfffd)
(when ioffsets (fxvector-set! ioffsets j bytes))
(fx+ j 1)]
[else (err who tp info i iend bytes b1 b2 ...)])]))
(define-syntax decode-eof-error
(syntax-rules ()
[(_ j i iend bytes)
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) (return #!eof iend iend #f (fx+ bytes (fx- iend i)) info)]
[(replace)
(string-set! str j #\xfffd)
(when ioffsets (fxvector-set! ioffsets j bytes))
(return (fx- (fx+ j 1) start) iend iend #f (fx+ bytes (fx- iend i)) info)]
[else (eof-err who tp info i iend bytes)])]))
(let loop ([j start]
[i (codec-info-next info)]
[iend (codec-info-iend info)]
[cr? (codec-info-icr info)]
[bytes 0])
(cond
[(fx= j jend) (return count i iend cr? bytes info)]
[(fx= i iend)
(if (fx= j start)
(let ([n (get-some-maybe-nb who bp bv 0 ifready?)])
(cond
[(eof-object? n) (return #!eof 0 0 #f bytes info)]
[(fx= n 0) (return 0 0 0 cr? bytes info)]
[else (loop j 0 n cr? bytes)]))
; don't try to read in this case to avoid dealing with eof
(return (fx- j start) i iend cr? bytes info))]
[(fx= i (fx- iend 1))
(bytevector-u8-set! bv 0 (bytevector-u8-ref bv i))
(let ([n (get-some-maybe-nb who bp bv 1 ifready?)])
(cond
[(eof-object? n) (decode-eof-error j 0 1 bytes)]
[(fx= n 0) (return (fx- j start) 0 1 cr? bytes info)]
[else (loop j 0 (fx+ n 1) cr? bytes)]))]
[else
(let ([b1 (bytevector-u8-ref bv i)]
[b2 (bytevector-u8-ref bv (fx+ i 1))])
(let ([w1 (if (codec-info-big info)
(fxlogor (fxsll b1 8) b2)
(fxlogor (fxsll b2 8) b1))])
(cond
[(and (fx= w1 #xfeff) (fx= i 0) (codec-info-bom info))
(when (and (port-has-port-position? bp)
(guard (c [#t #f])
(let ([n (port-position bp)])
(eq? (- n iend) 0))))
(codec-info-zbom-set! info #t))
(loop j (fx+ i 2) iend cr? (fx+ bytes 2))]
[(and (fx= w1 #xfffe) (fx= i 0) (codec-info-bom info))
(when (and (port-has-port-position? bp)
(guard (c [#t #f])
(let ([n (port-position bp)])
(eq? (- n iend) 0))))
(codec-info-zbom-set! info #t))
(codec-info-big-set! info (not (codec-info-big info)))
(loop j (fx+ i 2) iend cr? (fx+ bytes 2))]
[(fx<= #xD800 w1 #xDBFF) ; two-word encoding
(cond
[(fx<= i (fx- iend 4))
(let ([b3 (bytevector-u8-ref bv (fx+ i 2))]
[b4 (bytevector-u8-ref bv (fx+ i 3))])
(let ([w2 (if (codec-info-big info)
(fxlogor (fxsll b3 8) b4)
(fxlogor (fxsll b4 8) b3))])
(cond
[(fx<= #xDC00 w2 #xDFFF) ; valid encoding
(string-set! str j
(integer->char
(fx+ (fxlogor (fxsll (fx- w1 #xD800) 10) (fx- w2 #xDC00))
#x10000)))
(loop (fx+ j 1) (fx+ i 4) iend #f (fx+ bytes 4))]
[else
(let ([i (fx+ i 4)])
(let ([j (decode-error j i iend bytes b1 b2 b3 b4)])
(loop j i iend #f (fx+ bytes 4))))])))]
[(fx= i (fx- iend 2))
(bytevector-u8-set! bv 0 b1)
(bytevector-u8-set! bv 1 b2)
(let ([n (get-some-maybe-nb who bp bv 2 ifready?)])
(cond
[(eof-object? n) (decode-eof-error j 0 2 bytes)]
[(fx= n 0) (return (fx- j start) 0 2 cr? bytes info)]
[else (loop j 0 (fx+ n 2) cr? bytes)]))]
[else ; must have three bytes of the four we need
(bytevector-u8-set! bv 0 b1)
(bytevector-u8-set! bv 1 b2)
(bytevector-u8-set! bv 2 (bytevector-u8-ref bv (fx+ i 2)))
(let ([n (get-some-maybe-nb who bp bv 3 ifready?)])
(cond
[(eof-object? n) (decode-eof-error j 0 3 bytes)]
[(fx= n 0) (return (fx- j start) 0 3 cr? bytes info)]
[else (loop j 0 (fx+ n 3) cr? bytes)]))])]
[(fx<= #xDC00 w1 #xDFFF) ; bogus encoding
(let ([i (fx+ i 2)])
(let ([j (decode-error j i iend bytes b1 b2)])
(loop j i iend #f (fx+ bytes 2))))]
[(fx= w1 #x0d)
(cond
[(eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(string-set! str j #\return)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 2) iend cr? (fx+ bytes 2))]
[else
(string-set! str j #\newline)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 2) iend #t (fx+ bytes 2))])]
[(fx= w1 #x0a) ; LF
(cond
[cr? (loop j (fx+ i 2) iend #f (fx+ bytes 2))]
[else
(string-set! str j #\newline)
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 2) iend cr? (fx+ bytes 2))])]
[(fx= w1 #x85) ; NEL
(cond
[cr? (loop j (fx+ i 2) iend #f (fx+ bytes 2))]
[else
(string-set! str j
(if (eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(integer->char w1)
#\newline))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 2) iend cr? (fx+ bytes 2))])]
[(fx= w1 #x2028) ; LS
(string-set! str j
(if (eq? ($transcoder-eol-style (codec-info-tx info)) 'none)
(integer->char w1)
#\newline))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 2) iend #f (fx+ bytes 2))]
[else
(string-set! str j (integer->char w1))
(when ioffsets (fxvector-set! ioffsets j bytes))
(loop (fx+ j 1) (fx+ i 2) iend #f (fx+ bytes 2))])))])))))))
(define utf-16-encode
(let ()
(define (return ans o info)
(codec-info-next-set! info o)
ans)
(lambda (who tp str start count)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(when (codec-info-bom info)
(codec-info-bom-set! info #f)
(when (and (port-has-port-position? bp)
(guard (c [#t #f])
(eq? (port-position bp) 0)))
(codec-info-zbom-set! info #t))
(call-port-handler put-some who bp
(if (codec-info-big info) #vu8(#xfe #xff) #vu8(#xff #xfe))
0 2))
(let loop ([j start] [o (codec-info-next info)])
(cond
[(fx= j jend) (return count o info)]
[(fx>= o (fx- codec-buffer-length 1))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(loop j new-o)))]
[else
(let ([x (char->integer (string-ref str j))])
(cond
[(fx= x #x0a)
(let ([eol-style ($transcoder-eol-style (codec-info-tx info))])
(case eol-style
[(lf none)
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o #x0)
(bytevector-u8-set! bv (fx+ o 1) #x0a)]
[else
(bytevector-u8-set! bv (fx+ o 1) #x0)
(bytevector-u8-set! bv o #x0a)])
(loop (fx+ j 1) (fx+ o 2))]
[(cr)
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o #x0)
(bytevector-u8-set! bv (fx+ o 1) #x0d)]
[else
(bytevector-u8-set! bv (fx+ o 1) #x0)
(bytevector-u8-set! bv o #x0d)])
(loop (fx+ j 1) (fx+ o 2))]
[(nel)
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o #x0)
(bytevector-u8-set! bv (fx+ o 1) #x85)]
[else
(bytevector-u8-set! bv (fx+ o 1) #x0)
(bytevector-u8-set! bv o #x85)])
(loop (fx+ j 1) (fx+ o 2))]
[(ls)
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o #x20)
(bytevector-u8-set! bv (fx+ o 1) #x28)]
[else
(bytevector-u8-set! bv (fx+ o 1) #x20)
(bytevector-u8-set! bv o #x28)])
(loop (fx+ j 1) (fx+ o 2))]
[(crlf crnel)
(if (fx< o (fx- codec-buffer-length 3))
(begin
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o #x0)
(bytevector-u8-set! bv (fx+ o 1) #x0d)
(bytevector-u8-set! bv (fx+ o 2) #x0)
(bytevector-u8-set! bv (fx+ o 3)
(case eol-style [(crlf) #x0a] [(crnel) #x85]))]
[else
(bytevector-u8-set! bv (fx+ o 1) #x0)
(bytevector-u8-set! bv o #x0d)
(bytevector-u8-set! bv (fx+ o 3) #x0)
(bytevector-u8-set! bv (fx+ o 2)
(case eol-style [(crlf) #x0a] [(crnel) #x85]))])
(loop (fx+ j 1) (fx+ o 4)))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(loop j new-o))))]
[else ($oops who "unrecognized eol style ~s" eol-style)]))]
[(fx<= x #xffff) ; two-byte encoding
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o (fxsrl x 8))
(bytevector-u8-set! bv (fx+ o 1) (fxand x #xff))]
[else
(bytevector-u8-set! bv (fx+ o 1) (fxsrl x 8))
(bytevector-u8-set! bv o (fxand x #xff))])
(loop (fx+ j 1) (fx+ o 2))]
[else ; four-byte encoding
(if (fx< o (fx- codec-buffer-length 3))
(let ([x (fx- x #x10000)])
(let ([w1 (fxior #xd800 (fxsrl x 10))]
[w2 (fxior #xdc00 (fxand x #x3ff))])
(cond
[(codec-info-big info)
(bytevector-u8-set! bv o (fxsrl w1 8))
(bytevector-u8-set! bv (fx+ o 1) (fxand w1 #xff))
(bytevector-u8-set! bv (fx+ o 2) (fxsrl w2 8))
(bytevector-u8-set! bv (fx+ o 3) (fxand w2 #xff))]
[else
(bytevector-u8-set! bv (fx+ o 1) (fxsrl w1 8))
(bytevector-u8-set! bv o (fxand w1 #xff))
(bytevector-u8-set! bv (fx+ o 3) (fxsrl w2 8))
(bytevector-u8-set! bv (fx+ o 2) (fxand w2 #xff))])
(loop (fx+ j 1) (fx+ o 4))))
(let ([new-o (flush-buffer who bp bv 0 o)])
(if (fx= new-o o)
(return (fx- j start) o info)
(loop j new-o))))]))])))))))
(define make-utf-16-codec
(lambda (bom big)
(make-codec
[name "utf-16"]
[make-info
(lambda (who tx bp bv)
(make-codec-info tx bp bv 0 0
(and (input-port? bp) (make-fxvector (bytevector-length bv)))
0 #f bom #f big
utf-16-decode utf-16-encode (lambda (info) #f)))])))
(let ([codec-bom-be (make-utf-16-codec #t #t)]
[codec-bom-le (make-utf-16-codec #t #f)])
(set-who! #(r6rs: utf-16-codec)
(lambda () codec-bom-be))
(set-who! utf-16-codec
(case-lambda
[() codec-bom-be]
[(eness)
(unless (memq eness '(big little)) ($oops who "invalid endianness ~s" eness))
(if (eq? eness 'big) codec-bom-be codec-bom-le)])))
(set-who! utf-16le-codec
(let ([codec (make-utf-16-codec #f #f)])
(lambda () codec)))
(set-who! utf-16be-codec
(let ([codec (make-utf-16-codec #f #t)])
(lambda () codec))))
(when-feature iconv
(let ()
(define-record-type iconv-info
(parent codec-info)
(nongenerative)
(opaque #t)
(fields decode-desc encode-desc))
(define $iconv-open (foreign-procedure "(cs)s_iconv_open" (string string) ptr))
(define $iconv-close (foreign-procedure "(cs)s_iconv_close" (uptr) void))
(define $iconv-from-string (foreign-procedure "(cs)s_iconv_from_string" (uptr ptr uptr uptr ptr uptr uptr) ptr))
(define $iconv-to-string (foreign-procedure "(cs)s_iconv_to_string" (uptr ptr uptr uptr ptr uptr uptr) ptr))
(define iconv-decode
(let ()
(define (err who tp info i iend bv)
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
(codec-info-icr-set! info #f)
(let ([ls (let f ([k 4] [i i])
(if (fx= k 0)
(list "etc")
(if (fx= i iend)
(list "#!eof")
(cons (format "#x~2,'0x" (bytevector-u8-ref bv i))
(f (fx- k 1) (fx+ i 1))))))])
(decode-oops who tp "decoding failed for byte sequence ~a~{, ~a~}" (car ls) (cdr ls))))
(define (return-count str start count i iend info)
(let ([eol-style ($transcoder-eol-style (codec-info-tx info))])
(if (eq? eol-style 'none)
(return count i iend info)
(let ([end (fx+ start count)])
(let loop ([jold start] [jnew start] [cr? (codec-info-icr info)])
(if (fx= jold end)
(return/cr (fx- jnew start) i iend cr? info)
(let ([c (string-ref str jold)])
(case c
[(#\nel #\newline)
(if cr?
(loop (fx+ jold 1) jnew #f)
(begin
(string-set! str jnew #\newline)
(loop (fx+ jold 1) (fx+ jnew 1) #f)))]
[(#\return)
(string-set! str jnew #\newline)
(loop (fx+ jold 1) (fx+ jnew 1) #t)]
[(#\ls)
(string-set! str jnew #\newline)
(loop (fx+ jold 1) (fx+ jnew 1) #f)]
[else
(string-set! str jnew c)
(loop (fx+ jold 1) (fx+ jnew 1) #f)]))))))))
(define (return/cr ans i iend cr? info)
(codec-info-icr-set! info cr?)
(return ans i iend info))
(define (return ans i iend info)
(codec-info-next-set! info i)
(codec-info-iend-set! info iend)
ans)
(lambda (who tp str start count ifready?)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(let loop ([j start]
[i (codec-info-next info)]
[iend (codec-info-iend info)])
(cond
[(fx= j jend) (return-count str start count i iend info)]
[(fx= i iend)
(if (fx= j start)
(let ([n (get-some-maybe-nb who bp bv 0 ifready?)])
(cond
[(eof-object? n) (return/cr #!eof i iend #f info)]
[(fx= n 0) (return 0 i iend info)]
[else (loop j 0 n)]))
; don't try to read in this case to avoid dealing with eof
(return-count str start (fx- j start) i iend info))]
[else
(let ([newi.newj ($iconv-to-string (iconv-info-decode-desc info) bv i iend str j jend)])
(cond
[(pair? newi.newj) (loop (cdr newi.newj) (car newi.newj) iend)]
; one of the following presumably happened:
; - too few input bytes to make progress
; - invalid input sequence found
; assuming problem can't have been too little output space since
; j != jend implies enough room for at least one character
[(or (eq? newi.newj (constant SICONV-INVALID))
; assuming bv is large enough to hold any valid encoding sequence
(and (eq? newi.newj (constant SICONV-DUNNO))
(and (fx= i 0) (fx= iend (bytevector-length bv)))))
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) (loop j (fx+ i 1) iend)]
[(replace)
(string-set! str j #\xfffd)
(loop (fx+ j 1) (fx+ i 1) iend)]
[else (err who tp info i iend bv)])]
[else
; try again with more bytes
(unless (fx= i 0) (bytevector-copy! bv i bv 0 (fx- iend i)))
(let ([i 0] [iend (fx- iend i)])
(let ([n (get-some-maybe-nb who bp bv iend ifready?)])
(cond
[(eof-object? n)
(set-port-eof! bp #t)
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) (loop j (fx+ i 1) iend)]
[(replace)
(string-set! str j #\xfffd)
(loop (fx+ j 1) (fx+ i 1) iend)]
[else (err who tp info i iend bv)])]
[(fx= n 0) (return 0 i iend info)]
[else (loop j 0 (fx+ iend n))])))]))])))))))
(define iconv-encode
(let ()
(define (return ans o info)
(codec-info-next-set! info o)
ans)
(define (do-iconv who info str j jend bv o)
(let ([eol-style ($transcoder-eol-style (codec-info-tx info))]
[desc (iconv-info-encode-desc info)])
(cond
[(memq eol-style '(none lf))
($iconv-from-string desc str j jend bv o codec-buffer-length)]
[(eqv? (string-ref str j) #\newline)
(let ()
(define (iconv-newline s k)
(let ([newj.newo ($iconv-from-string desc s 0 k bv o codec-buffer-length)])
(if (pair? newj.newo)
(if (fx= (car newj.newo) k)
(cons (fx+ j 1) (cdr newj.newo))
(constant SICONV-NOROOM))
newj.newo)))
(case eol-style
[(cr) (iconv-newline "\r" 1)]
[(nel) (iconv-newline "\x85;" 1)]
[(ls) (iconv-newline "\x2028;" 1)]
[(crlf) (iconv-newline "\r\n" 2)]
[(crnel) (iconv-newline "\r\x85;" 2)]
[else ($oops who "unrecognized eol style ~s" eol-style)]))]
[else
(do ([k (fx+ j 1) (fx+ k 1)])
((or (fx= k jend) (eqv? (string-ref str k) #\newline))
($iconv-from-string desc str j k bv o codec-buffer-length)))])))
(lambda (who tp str start count)
(let ([info ($port-info tp)])
(let ([bp (codec-info-bp info)]
[bv (codec-info-bv info)]
[jend (fx+ start count)])
(let loop ([j start] [o (codec-info-next info)])
(cond
[(fx= j jend) (return count o info)]
[(fx= o codec-buffer-length)
(let ([o (flush-buffer who bp bv 0 o)])
(if (fx= o codec-buffer-length)
(return (fx- j start) o info)
(loop j o)))]
[else
(let ([newj.newo (do-iconv who info str j jend bv o)])
(cond
[(pair? newj.newo) (loop (car newj.newo) (cdr newj.newo))]
; one of the following presumably happened:
; - unencodeable character found
; - too little output space to make progress
[(fx= o 0) ; assuming bv is large enough to hold any valid encoding sequence
(case ($transcoder-error-handling-mode (codec-info-tx info))
[(ignore) (loop (fx+ j 1) o)]
[(replace)
; try to write the Unicode replacement character
(let ([newj.newo ($iconv-from-string (iconv-info-encode-desc info) "\xfffd;" 0 1 bv o codec-buffer-length)])
(if (pair? newj.newo)
(loop (fx+ j 1) (cdr newj.newo))
; if that failed, try to write ?
(let ([newj.newo ($iconv-from-string (iconv-info-encode-desc info) "?" 0 1 bv o codec-buffer-length)])
(if (pair? newj.newo)
(loop (fx+ j 1) (cdr newj.newo))
; if even that failed, just ignore
(loop (fx+ j 1) o)))))]
[else (encode-oops who tp (string-ref str j))])]
[else (let ([newo (flush-buffer who bp bv 0 o)])
(if (fx= newo o)
(return (fx- j start) o info)
(loop j newo)))]))])))))))
(define iconv-close
(lambda (info)
(cond [(iconv-info-decode-desc info) => $iconv-close])
(cond [(iconv-info-encode-desc info) => $iconv-close])))
(set-who! iconv-codec
(lambda (code)
(unless (string? code) ($oops who "~s is not a string" code))
(make-codec
[name (format "iconv ~a" code)]
[make-info
(lambda (who tx bp bv)
(define UTF-32B/LE
(constant-case native-endianness
[(little) "UTF-32LE"]
[(big) "UTF-32BE"]))
(define (iconv-open to from)
(let ([desc ($iconv-open to from)])
(when (string? desc) ($oops who "~a" desc))
(unless desc ($oops who "unsupported encoding ~a" code))
desc))
(let ([decode-desc (and (input-port? bp) (iconv-open UTF-32B/LE code))]
[encode-desc (and (output-port? bp) (iconv-open code UTF-32B/LE))])
(make-iconv-info tx bp bv 0 0 #f 0 #f #f #f #f
(if decode-desc
iconv-decode
(lambda args ($oops who "unexpected decode from non-input-port ~s" bp)))
(if encode-desc
iconv-encode
(lambda args ($oops who "unexpected encode to non-output-port ~s" bp)))
iconv-close decode-desc encode-desc)))]))))))
;; eol-style in syntax.ss
(set-who! $eol-style?
(lambda (style) (and (memq style (eol-style-list)) #t)))
(set-who! native-eol-style
(lambda ()
(eol-style none)))
;; &i/o-decoding in exceptions.ss
;; make-i/o-decoding-error in exceptions.ss
;; i/o-decoding-error? in exceptions.ss
;; &i/o-encoding in exceptions.ss
;; make-i/o-encoding-error in exceptions.ss
;; i/o-encoding-error? in exceptions.ss
;; i/o-encoding-error-char in exceptions.ss
;; error-handling-mode in syntax.ss
(set-who! $error-handling-mode?
(lambda (mode) (and (memq mode (error-handling-mode-list)) #t)))
(set-who! make-transcoder
(rec make-transcoder
(case-lambda
[(codec) (make-transcoder codec (native-eol-style) (error-handling-mode replace))]
[(codec eol-style) (make-transcoder codec eol-style (error-handling-mode replace))]
[(codec eol-style handling-mode)
(unless (codec? codec) ($oops who "~s is not a codec" codec))
(unless ($eol-style? eol-style) ($oops who "~s is not an eol-style" eol-style))
(unless ($error-handling-mode? handling-mode)
($oops who "~s is not an error-handling-mode" handling-mode))
($make-transcoder codec eol-style handling-mode)])))
(set-who! transcoder? (lambda (x) ($transcoder? x)))
(let ([transcoder (make-transcoder (utf-8-codec))])
(set-who! native-transcoder (lambda () transcoder))
(set-who! current-transcoder
($make-thread-parameter transcoder
(lambda (tx)
(unless ($transcoder? tx) ($oops who "~s is not a transcoder" tx))
tx))))
;; transcoder-codec, transcoder-eol-style, transcoder-error-handling-mode
(let ()
(define-syntax define-accessor
(syntax-rules ()
[(_ name $name)
(set-who! name
(lambda (transcoder)
(unless ($transcoder? transcoder)
($oops who "~s is not a transcoder" transcoder))
($name transcoder)))]))
(define-accessor transcoder-codec $transcoder-codec)
(define-accessor transcoder-eol-style $transcoder-eol-style)
(define-accessor transcoder-error-handling-mode $transcoder-error-handling-mode))
;;;; 8.2.5 End-of-file object
;; eof-object in prims.ss
;; eof-object? in prims.ss
;;;; 8.2.6 Input and output ports
;; port? in prims.ss
(set-who! port-transcoder
(lambda (port)
(unless (port? port)
($oops who "~s is not a port" port))
(let ([info ($port-info port)])
(and (codec-info? info)
(codec-info-tx info)))))
;; textual-port? in prims.ss
;; binary-port? in prims.ss
;; transcoded-port
(let ()
(module (make-transcoded-port-handler)
(define read-from-codec
(lambda (who tp str start count ifready?)
(when (eq? tp $console-input-port)
(guard (c [else (void)]) (flush-output-port $console-output-port))
(unless (eq? $console-error-port $console-output-port)
(guard (c [else (void)]) (flush-output-port $console-error-port))))
((codec-info-decode ($port-info tp)) who tp str start count ifready?)))
(define fill-from-codec
(lambda (who tp ifready?)
(let ([buf (textual-port-input-buffer tp)])
(let ([n (read-from-codec who tp buf 0 (string-length buf) ifready?)])
(if (eof-object? n)
(begin
(set-textual-port-input-size! tp 0)
(set-port-eof! tp #t))
(set-textual-port-input-size! tp n))
n))))
(define write-to-codec
(lambda (who tp str start count)
(let ([n ((codec-info-encode ($port-info tp)) who tp str start count)])
(unless (fx= n 0)
(set-port-bol! tp (eol-char? (string-ref str (fx- (fx+ start n) 1)))))
n)))
(define flush-to-codec
(case-lambda
[(who tp) (flush-to-codec who tp (textual-port-output-index tp))]
[(who tp count)
(unless (fx= count 0)
; push the chars from port's buffer into the codec's buffer
(let loop ([start 0] [count count])
(let ([n (write-to-codec who tp (textual-port-output-buffer tp) start count)])
(unless (fx= n count) (loop (fx+ start n) (fx- count n)))))
(if ($port-flags-set? tp (constant port-flag-line-buffered))
(set-textual-port-output-size! tp 0)
(set-textual-port-output-index! tp 0)))]))
(define try-flush-to-codec
(lambda (who tp)
(let ([count (textual-port-output-index tp)])
(or (fx= count 0)
(let ([buf (textual-port-output-buffer tp)])
(let loop ([start 0] [count count])
(let ([n (write-to-codec who tp buf start count)])
(cond
[(fx= n count)
(if ($port-flags-set? tp (constant port-flag-line-buffered))
(set-textual-port-output-size! tp 0)
(set-textual-port-output-index! tp 0))
#t]
[(fx= n 0)
(unless (fx= start 0)
(string-copy! buf start buf 0 count)
(when ($port-flags-set? tp (constant port-flag-line-buffered))
(set-textual-port-output-size! tp count))
(set-textual-port-output-index! tp count))
#f]
[else (loop (fx+ start n) (fx- count n))]))))))))
(define flush-from-codec
(lambda (who tp)
; push the bytes from codec's buffer into the binary port
(let ([info ($port-info tp)])
(let loop ([start 0] [count (codec-info-next info)])
(unless (fx= count 0)
(let ([n (let ([bp (codec-info-bp info)])
(call-port-handler put-some who bp (codec-info-bv info) start count))])
(loop (fx+ start n) (fx- count n)))))
(codec-info-next-set! info 0))))
(define flush-from-bp
(lambda (who tp)
(let ([bp (codec-info-bp ($port-info tp))])
(call-port-handler flush who bp))))
(module ((make-ready-for-input $make-ready-for-input))
(define $make-ready-for-input
(lambda (who tp)
(flush-to-codec who tp)
(flush-from-codec who tp)
(set-textual-port-output-size! tp 0)
(let ([info ($port-info tp)])
(codec-info-next-set! info 0)
(codec-info-iend-set! info 0)
(codec-info-icr-set! info #f))
($set-port-flags! tp (constant port-flag-input-mode))))
(define-syntax make-ready-for-input
(syntax-rules ()
[(_ who ?tp)
(let ([tp ?tp])
(unless ($port-flags-set? tp (constant port-flag-input-mode))
($make-ready-for-input who tp)))])))
(module ((make-ready-for-output $make-ready-for-output))
(define $make-ready-for-output
(lambda (who tp)
; rewind if textual port or codec has something buffered.
; if underlying binary port has something buffered, we'll let
; the first write to the binary port take care of it
(unless (and (fx= (textual-port-input-size tp) 0)
(let ([info ($port-info tp)])
(fx= (codec-info-next info) (codec-info-iend info))))
(if (port-handler-port-position ($port-handler tp))
(if (port-handler-set-port-position! ($port-handler tp))
(let ([bp (codec-info-bp ($port-info tp))])
(call-port-handler set-port-position! who bp
(call-port-handler port-position who tp)))
(position-warning who "cannot set position for write after read on ~s" tp))
(position-warning who "cannot determine position for write after read on ~s" tp)))
(set-textual-port-input-size! tp 0)
(set-port-eof! tp #f)
(codec-info-next-set! ($port-info tp) 0)
(unless ($port-flags-set? tp (constant port-flag-line-buffered))
(set-textual-port-output-size! tp (fx1- (string-length (textual-port-output-buffer tp)))))
($reset-port-flags! tp (constant port-flag-input-mode))))
(define-syntax make-ready-for-output
(syntax-rules ()
[(_ ?who ?tp)
(let ([tp ?tp])
(when ($port-flags-set? tp (constant port-flag-input-mode))
($make-ready-for-output ?who tp)))])))
(define contains-eol-char?
(lambda (s i end)
(let f ([i i])
(and (not (fx= i end))
(or (eol-char? (string-ref s i))
(f (fx+ i 1)))))))
(define transcoded-port-ready?
(lambda (who tp)
(assert-not-closed who tp)
(make-ready-for-input who tp)
(or (not (port-input-empty? tp))
(port-flag-eof-set? tp)
(not (eq? (fill-from-codec who tp #t) 0)))))
(define transcoded-port-lookahead
(lambda (who tp)
(assert-not-closed who tp)
(make-ready-for-input who tp)
(cond
[(not (port-input-empty? tp))
(string-ref (textual-port-input-buffer tp)
(textual-port-input-index tp))]
[(port-flag-eof-set? tp) (eof-object)]
[else (let loop ()
(let ([n (fill-from-codec who tp #f)])
(cond
[(eq? n 0) (loop)]
[(eof-object? n) n]
[else (string-ref (textual-port-input-buffer tp) 0)])))])))
(define transcoded-port-unget
(lambda (who tp x)
(assert-not-closed who tp)
(make-ready-for-input who tp)
(when (port-flag-eof-set? tp) (unget-error who tp x))
(if (eof-object? x)
(let ()
(unless (port-input-empty? tp) (unget-error who tp x))
(set-port-eof! tp #t))
(let ([index (textual-port-input-index tp)])
(when (fx= index 0) (unget-error who tp x))
(set-textual-port-input-index! tp (fx- index 1))))))
(define transcoded-port-get
(lambda (who tp)
(assert-not-closed who tp)
(make-ready-for-input who tp)
(cond
[(not (port-input-empty? tp))
(let ([index (textual-port-input-index tp)])
(set-textual-port-input-index! tp (fx1+ index))
(string-ref (textual-port-input-buffer tp) index))]
[(port-flag-eof-set? tp) (set-port-eof! tp #f) (eof-object)]
[else (let loop ()
(let ([n (fill-from-codec who tp #f)])
(cond
[(eq? 0 n) (loop)]
[(eof-object? n) (set-port-eof! tp #f) (eof-object)]
[else
(set-textual-port-input-index! tp 1)
(string-ref (textual-port-input-buffer tp) 0)])))])))
(define transcoded-port-get-some
(lambda (who tp str start count)
(assert-not-closed who tp)
(make-ready-for-input who tp)
(let ([port-count (textual-port-input-count tp)])
(cond
[(not (fx= port-count 0))
(let ([count (fxmin count port-count)]
[index (textual-port-input-index tp)])
(string-copy! (textual-port-input-buffer tp) index str start count)
(set-textual-port-input-index! tp (fx+ index count))
count)]
[(port-flag-eof-set? tp) (set-port-eof! tp #f) (eof-object)]
[else (read-from-codec who tp str start count #f)]))))
(define transcoded-port-clear-input
(lambda (who tp)
(assert-not-closed who tp)
(when ($port-flags-set? tp (constant port-flag-input-mode))
; position will be wrong after this. c'est la vie.
(set-textual-port-input-size! tp 0)
(set-port-eof! tp #f)
(let ([info ($port-info tp)])
(codec-info-next-set! info 0)
(codec-info-iend-set! info 0)
(codec-info-icr-set! info #f)
(let ([bp (codec-info-bp info)])
(call-port-handler clear-input who bp))))))
(define transcoded-port-put
(lambda (who tp elt)
(assert-not-closed who tp)
(make-ready-for-output who tp)
(let ([index (textual-port-output-index tp)])
(string-set! (textual-port-output-buffer tp) index elt)
(let ([index (fx+ index 1)])
(cond
[(not (port-output-full? tp))
(set-textual-port-output-index! tp index)]
[($port-flags-set? tp (constant port-flag-line-buffered))
(cond
[(eol-char? elt)
(flush-to-codec who tp index)
(flush-from-codec who tp)
(flush-from-bp who tp)]
[(fx< (textual-port-output-size tp) (fx- (string-length (textual-port-output-buffer tp)) 1))
(set-textual-port-output-size! tp index)
(set-textual-port-output-index! tp index)]
[else (flush-to-codec who tp index)])]
[else (flush-to-codec who tp index)])))))
(define transcoded-port-put-some
(lambda (who tp str start count)
(assert-not-closed who tp)
(make-ready-for-output who tp)
(cond
[($port-flags-set? tp (constant port-flag-line-buffered))
(if (contains-eol-char? str start (fx+ start count))
(begin
; line-buffering trumps nonblocking
(flush-to-codec who tp)
(let loop ([start start] [count count])
(unless (fx= count 0)
(let ([n (write-to-codec who tp str start count)])
(loop (fx+ start n) (fx- count n)))))
(flush-from-codec who tp)
(flush-from-bp who tp)
count)
(let ([buf (textual-port-output-buffer tp)]
[index (textual-port-output-index tp)])
(if (and (fx<= count max-put-copy) (fx< (fx+ index count) (string-length buf)))
; there's room to copy str with one character to spare
(begin
(string-copy! str start buf index count)
(let ([index (fx+ index count)])
(set-textual-port-output-size! tp index)
(set-textual-port-output-index! tp index))
count)
(if (try-flush-to-codec who tp) (write-to-codec who tp str start count) 0))))]
[else (if (try-flush-to-codec who tp) (write-to-codec who tp str start count) 0)])))
(define transcoded-port-flush
(lambda (who tp)
(assert-not-closed who tp)
(make-ready-for-output who tp)
(flush-to-codec who tp)
(flush-from-codec who tp)
(flush-from-bp who tp)))
(define transcoded-port-clear-output
(lambda (who tp)
(assert-not-closed who tp)
(unless ($port-flags-set? tp (constant port-flag-input-mode))
; position will be wrong after this. c'est la vie.
(if ($port-flags-set? tp (constant port-flag-line-buffered))
(set-textual-port-output-size! tp 0)
(set-textual-port-output-index! tp 0))
(let ([info ($port-info tp)])
(codec-info-next-set! info 0)
(let ([bp (codec-info-bp info)])
(call-port-handler clear-output who bp))))))
(define transcoded-port-close-port
(lambda (who tp)
(unless (port-closed? tp)
(when (output-port? tp)
(make-ready-for-output who tp)
(flush-to-codec who tp)
(flush-from-codec who tp)
(flush-from-bp who tp))
(unless (or (eq? tp $console-input-port) ; refuse to close original console ports
(eq? tp $console-output-port)
(eq? tp $console-error-port))
(when (output-port? tp)
(set-textual-port-output-size! tp 0))
(when (input-port? tp)
(set-textual-port-input-size! tp 0)
(set-port-eof! tp #f))
(let ([info ($port-info tp)])
(close-port (codec-info-bp info))
((codec-info-close info) info))
(unregister-open-file tp)
(mark-port-closed! tp)))))
(define transcoded-port-port-position
(lambda (who tp)
(assert-not-closed who tp)
(cond
[($port-flags-set? tp (constant port-flag-input-mode))
; (port-position bp) gives us position in bytes after characters and bytes
; we haven't yet consumed. to get position of first unconsumed character or
; byte, need to adjust downward by the number of bytes buffered, using
; ioffsets to determine the byte position of the first unconsumed character
; relative to the start of the port's buffer, ibytes to determine the total
; number of bytes represented by the characters in the port's buffer, and
; (- iend next) to determine the number of bytes not yet converted
; into characters. if ioffsets is not available, the reported port-position
; may not be accurate.
(let ([info ($port-info tp)])
(- (call-port-handler port-position who (codec-info-bp info))
(let ([buffered-bytes (fx- (codec-info-iend info) (codec-info-next info))])
(cond
[(port-input-empty? tp) buffered-bytes]
[(codec-info-ioffsets info) =>
(lambda (ioffsets)
(fx- (fx+ (codec-info-ibytes info) buffered-bytes)
(fxvector-ref ioffsets (textual-port-input-index tp))))]
[else
(position-warning who "cannot determine accurate position for operation on ~s" tp)
buffered-bytes]))))]
[else
(flush-to-codec who tp)
(flush-from-codec who tp)
(let ([bp (codec-info-bp ($port-info tp))])
(call-port-handler port-position who bp))])))
(define transcoded-port-set-port-position!
(lambda (who tp pos)
(assert-not-closed who tp)
(let ([info ($port-info tp)])
(if ($port-flags-set? tp (constant port-flag-input-mode))
(begin
(set-textual-port-input-size! tp 0)
(set-port-eof! tp #f)
(codec-info-next-set! info 0)
(codec-info-iend-set! info 0)
(codec-info-icr-set! info #f))
(begin
(flush-to-codec who tp)
(flush-from-codec who tp)))
(let ([bp (codec-info-bp info)])
(call-port-handler set-port-position! who bp
; position past bom if known to be present at position 0
; if it was found or put elsewhere, all bets are off
(if (and (eq? pos 0) (codec-info-zbom info)) 2 pos))))))
(define transcoded-port-port-length
(lambda (who tp)
(assert-not-closed who tp)
(unless ($port-flags-set? tp (constant port-flag-input-mode))
(flush-to-codec who tp)
(flush-from-codec who tp))
(let ([bp (codec-info-bp ($port-info tp))])
(call-port-handler port-length who bp))))
(define transcoded-port-set-port-length!
(lambda (who tp pos)
(assert-not-closed who tp)
(unless ($port-flags-set? tp (constant port-flag-input-mode))
(flush-to-codec who tp)
(flush-from-codec who tp))
(let ([bp (codec-info-bp ($port-info tp))])
(call-port-handler set-port-length! who bp pos))))
(define transcoded-port-port-nonblocking?
(lambda (who tp)
(assert-not-closed who tp)
(port-nonblocking? (codec-info-bp ($port-info tp)))))
(define transcoded-port-set-port-nonblocking!
(lambda (who tp b)
(assert-not-closed who tp)
(set-port-nonblocking! (codec-info-bp ($port-info tp)) b)))
(define (make-transcoded-port-handler bp)
; could cache these, but the savings would be minimal
(make-port-handler
[ready? (and (input-port? bp) transcoded-port-ready?)]
[lookahead (and (input-port? bp) transcoded-port-lookahead)]
[unget (and (input-port? bp) transcoded-port-unget)]
[get (and (input-port? bp) transcoded-port-get)]
[get-some (and (input-port? bp) transcoded-port-get-some)]
[clear-input (and (input-port? bp) transcoded-port-clear-input)]
[put (and (output-port? bp) transcoded-port-put)]
[put-some (and (output-port? bp) transcoded-port-put-some)]
[flush (and (output-port? bp) transcoded-port-flush)]
[clear-output (and (output-port? bp) transcoded-port-clear-output)]
[close-port transcoded-port-close-port]
[port-position
(and (port-handler-port-position ($port-handler bp))
transcoded-port-port-position)]
[set-port-position!
(and (port-handler-set-port-position! ($port-handler bp))
transcoded-port-set-port-position!)]
[port-length
(and (port-handler-port-length ($port-handler bp))
transcoded-port-port-length)]
[set-port-length!
(and (port-handler-set-port-length! ($port-handler bp))
transcoded-port-set-port-length!)]
[port-nonblocking?
(and (port-handler-port-nonblocking? ($port-handler bp))
transcoded-port-port-nonblocking?)]
[set-port-nonblocking!
(and (port-handler-set-port-nonblocking! ($port-handler bp))
transcoded-port-set-port-nonblocking!)])))
(set-who! transcoded-port
(lambda (bp tx)
(define-syntax copy-flag!
(syntax-rules ()
[(_ from to flag)
(when ($port-flags-set? from (constant flag))
($set-port-flags! to (constant flag)))]))
(define (clone-port bp)
(let ([bpc ($make-textual-input/output-port "" ($port-handler bp) "" "" #f)])
($byte-copy! bp (constant port-type-disp) bpc (constant port-type-disp) (constant size-port))
bpc))
(unless (and (port? bp) (binary-port? bp)) ($oops who "~s is not a binary port" bp))
(unless ($transcoder? tx) ($oops who "~s is not a transcoder" tx))
(let* ([bpc (clone-port bp)]
[name (port-name bpc)]
[buffer-length (if (or ($port-flags-set? bp (constant port-flag-block-buffered))
($port-flags-set? bp (constant port-flag-line-buffered)))
buffered-transcoded-port-buffer-length
unbuffered-transcoded-port-buffer-length)]
[codec ($transcoder-codec tx)]
[info ((codec-make-info codec) who tx bpc (make-bytevector codec-buffer-length))]
[handler (make-transcoded-port-handler bpc)]
[tp (if (input-port? bpc)
(if (output-port? bpc)
($make-textual-input/output-port name handler
(make-string buffer-length)
(make-string buffer-length)
info)
($make-textual-input-port name handler
(make-string buffer-length) info))
($make-textual-output-port name handler
(make-string buffer-length) info))])
(copy-flag! bpc tp port-flag-block-buffered)
(copy-flag! bpc tp port-flag-line-buffered)
(mark-port-closed! bp)
(when (input-port? bp)
(set-binary-port-input-size! bp 0)
(set-port-eof! bp #f)
(set-textual-port-input-size! tp 0))
(when (output-port? bp)
(set-binary-port-output-size! bp 0)
(set-textual-port-output-size! tp
(if ($port-flags-set? tp (constant port-flag-line-buffered))
0
(fx1- buffer-length)))
($set-port-flags! tp (constant port-flag-bol)))
($set-port-info! bp tp) ; back-link for bytevector-output-port extractor
(when (registered-open-file? bp)
(unregister-open-file bp)
(register-open-file tp))
tp))))
(let ()
(define-syntax set-who!-port-has
(lambda (x)
(syntax-case x ()
[(_ name)
(with-syntax ([name (construct-name #'name "port-has-" #'name "?")]
[field (construct-name #'name "port-handler-" #'name)])
#'(set-who! name
(lambda (p)
(unless (port? p) ($oops who "~s is not a port" p))
(and (field ($port-handler p)) #t))))])))
(define-syntax set-who!-port
(lambda (x)
(syntax-case x ()
[(_ name (args ...))
(with-syntax ([field (construct-name #'name "port-handler-" #'name)])
#'(set-who! name
(lambda (p args ...)
(unless (port? p) ($oops who "~s is not a port" p))
(let ([op (field ($port-handler p))])
(unless op ($oops who "~s does not support operation" p))
(op who p args ...)))))])))
(set-who!-port-has port-position)
(set-who!-port port-position ())
(set-who!-port-has set-port-position!)
(set-who!-port set-port-position! (x))
;; The following are not in R6RS
(set-who!-port-has port-nonblocking?)
(set-who!-port port-nonblocking? ())
(set-who!-port-has set-port-nonblocking!)
(set-who!-port set-port-nonblocking! (x))
(set-who!-port-has port-length)
(set-who!-port port-length ())
(set-who!-port-has set-port-length!)
(set-who!-port set-port-length! (x)))
(set-who! file-position
(case-lambda
[(p)
(unless (port? p) ($oops who "~s is not a port" p))
(let ([op (port-handler-port-position ($port-handler p))])
(unless op ($oops who "~s does not support operation" p))
(op who p))]
[(p pos)
(unless (port? p) ($oops who "~s is not a port" p))
(let ([op (port-handler-set-port-position! ($port-handler p))])
(unless op ($oops who "~s does not support operation" p))
(op who p pos))]))
(set-who! file-length
(lambda (p)
(unless (port? p) ($oops who "~s is not a port" p))
(let ([op (port-handler-port-length ($port-handler p))])
(unless op ($oops who "~s does not support operation" p))
(op who p))))
;; Not in R6RS
;; truncate-file is set-port-length and set-port-position combined
(let ()
(define (tp who port pos)
(unless (output-port? port) ($oops who "~s is not an output port" port))
(let ([handler ($port-handler port)])
(let ([set-len! (port-handler-set-port-length! handler)]
[set-pos! (port-handler-set-port-position! handler)])
(unless (and set-len! set-pos!)
($oops who "~s does not support operation" port))
(set-len! who port pos)
(set-pos! who port pos))))
(set-who! truncate-port
(case-lambda
[(port) (tp who port 0)]
[(port pos) (tp who port pos)]))
(set-who! truncate-file
(case-lambda
[(port) (tp who port 0)]
[(port pos) (tp who port pos)])))
(set-who! close-port
(lambda (port)
(unless (port? port) ($oops who "~s is not a port" port))
(call-port-handler close-port who port)))
(set-who! call-with-port
(lambda (port proc)
(unless (port? port) ($oops who "~s is not a port" port))
(unless (procedure? proc) ($oops who "~s is not a procedure" proc))
(call-with-values
(lambda () (proc port))
(case-lambda
[(x)
(call-port-handler close-port who port)
x]
[args
(call-port-handler close-port who port)
(apply values args)]))))
;;;; 8.2.7 Input ports
;; input-port? in prims.ss
;; port-eof? in prims.ss
;; Not in R6RS
(set-who! input-port-ready?
(lambda (input-port)
(unless (input-port? input-port)
($oops who "~s is not an input port" input-port))
(or (not (port-input-empty? input-port))
(port-flag-eof-set? input-port)
(call-port-handler ready? who input-port))))
(let ()
;; open-file-input-port
(define open-binary-file-input-port
(lambda (who filename options mode)
(unless (string? filename)
($oops who "~s is not a string" filename))
(unless (and (enum-set? options)
(enum-set-subset? options $file-options))
($oops who "~s is not a file-options object" options))
(unless (buffer-mode? mode)
($oops who "~s is not a valid buffer mode" mode))
(when (enum-set-subset? (file-options exclusive) options)
($oops who "exclusive option not supported for file input ports"))
(let ([fd (critical-section ($open-input-fd filename (enum-set-subset? (file-options compressed) options)))])
(when (pair? fd) (open-oops who filename options fd))
(if (box? fd) ; box iff file opened with compressed option is actually gzip'd
(open-binary-fd-input-port who filename (unbox fd) #t mode #t)
(open-binary-fd-input-port who filename fd #t mode #f)))))
(define open-binary-standard-input-port
(lambda (b-mode)
(define who 'standard-input-port)
(unless (buffer-mode? b-mode)
($oops who "~s is not a valid buffer mode" b-mode))
(open-binary-fd-input-port who "stdin" (make-fd 0) ($fd-regular? 0) b-mode #f)))
(define help-open-file-input-port
(lambda (who filename options buffer-mode maybe-transcoder)
(let ([binary-port (open-binary-file-input-port who filename options buffer-mode)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))))
(set-who! port-file-compressed!
(lambda (p)
(unless (port? p) ($oops who "~s is not a port" p))
(when (and (input-port? p) (output-port? p)) ($oops who "cannot compress input/output port ~s" p))
(let ([bp (if (binary-port? p)
p
(let ([info ($port-info p)])
(and (codec-info? info) (codec-info-bp info))))])
(unless (and bp ($port-flags-set? bp (constant port-flag-file))) ($oops who "~s is not a file port" p))
(unless ($port-flags-set? bp (constant port-flag-compressed))
(let ([fd ($port-info bp)])
(unless ($fd-regular? fd) ($oops who "~s is not a regular file" p))
; flush any uncompressed data in the output buffer
(when (output-port? p) (flush-output-port p))
(critical-section
(let ([gzfd (if (input-port? p)
(let ([fp (port-position p)])
; reposition to 'unread' any compressed data in the input buffer
(set-port-position! p fp)
($compress-input-fd fd fp))
($compress-output-fd fd))])
(when (string? gzfd) ($oops who "failed for ~s: ~(~a~)" p gzfd))
(unless (eqv? gzfd fd) ; uncompressed input port
(assert (box? gzfd))
($set-port-info! bp (unbox gzfd))
($set-port-flags! bp (constant port-flag-compressed))))))))))
(set-who! open-fd-input-port
(case-lambda
[(fd)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(open-binary-fd-input-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) (buffer-mode block) #f)]
[(fd buffer-mode)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(unless (buffer-mode? buffer-mode)
($oops who "~s is not a buffer mode" buffer-mode))
(open-binary-fd-input-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) buffer-mode #f)]
[(fd buffer-mode maybe-transcoder)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(unless (buffer-mode? buffer-mode)
($oops who "~s is not a buffer mode" buffer-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([binary-port (open-binary-fd-input-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) buffer-mode #f)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))]))
(let ()
(define s-process (foreign-procedure "(cs)s_process" (string boolean) scheme-object))
(define (subprocess-port who what fd pid b-mode maybe-transcoder)
(unless (buffer-mode? b-mode)
($oops who "~s is not a valid buffer mode" b-mode))
(let ([name (format "pid ~s ~a" pid what)])
(let ([bp (if (eq? what 'stdin)
(open-binary-fd-output-port who name (make-fd fd) #f b-mode #f #f)
(open-binary-fd-input-port who name (make-fd fd) #f b-mode #f))])
(if maybe-transcoder (transcoded-port bp maybe-transcoder) bp))))
(set-who! process
(lambda (s)
(unless (string? s) ($oops who "~s is not a string" s))
(apply (lambda (ifd ofd pid)
(list
(subprocess-port who 'stdout ifd pid (buffer-mode block) (current-transcoder))
(subprocess-port who 'stdin ofd pid (buffer-mode line) (current-transcoder))
pid))
(s-process s #f))))
(set-who! open-process-ports
(case-lambda
[(s)
(unless (string? s) ($oops who "~s is not a string" s))
(apply (lambda (ifd efd ofd pid)
(values
(subprocess-port who 'stdin ofd pid (buffer-mode block) #f)
(subprocess-port who 'stdout ifd pid (buffer-mode block) #f)
(subprocess-port who 'stderr efd pid (buffer-mode block) #f)
pid))
(s-process s #t))]
[(s b-mode)
(unless (string? s) ($oops who "~s is not a string" s))
(apply (lambda (ifd efd ofd pid)
(values
(subprocess-port who 'stdin ofd pid b-mode #f)
(subprocess-port who 'stdout ifd pid b-mode #f)
(subprocess-port who 'stderr efd pid b-mode #f)
pid))
(s-process s #t))]
[(s b-mode maybe-transcoder)
(unless (string? s) ($oops who "~s is not a string" s))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(apply (lambda (ifd efd ofd pid)
(values
(subprocess-port who 'stdin ofd pid b-mode maybe-transcoder)
(subprocess-port who 'stdout ifd pid b-mode maybe-transcoder)
(subprocess-port who 'stderr efd pid b-mode maybe-transcoder)
pid))
(s-process s #t))])))
(set-who! open-file-input-port
(case-lambda
[(filename)
(open-binary-file-input-port who filename (file-options) (buffer-mode block))]
[(filename options)
(open-binary-file-input-port who filename options (buffer-mode block))]
[(filename options buffer-mode)
(open-binary-file-input-port who filename options buffer-mode)]
[(filename options buffer-mode maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(help-open-file-input-port who filename options buffer-mode maybe-transcoder)]))
(set! $open-file-input-port
(case-lambda
[(who filename)
(open-binary-file-input-port who filename (file-options) (buffer-mode block))]
[(who filename options)
(open-binary-file-input-port who filename options (buffer-mode block))]
[(who filename options buffer-mode)
(open-binary-file-input-port who filename options buffer-mode)]
[(who filename options buffer-mode maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(help-open-file-input-port who filename options buffer-mode maybe-transcoder)]))
(set-who! standard-input-port
(case-lambda
[() (open-binary-standard-input-port (buffer-mode block))]
[(b-mode) (open-binary-standard-input-port b-mode)]
[(b-mode maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([binary-port (open-binary-standard-input-port b-mode)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))]))
(set-who! r6rs:standard-input-port
(rec standard-input-port
(lambda ()
(open-binary-standard-input-port (buffer-mode block)))))
; simple i/o routines here to share helpers
(let ()
(define (oif who s o)
(unless (string? s) ($oops who "~s is not a string" s))
(let ([o (if (list? o) o (list o))])
(let loop ([o o] [zmode #f] [bmode #f])
(if (null? o)
(help-open-file-input-port who s
(if (eq? zmode 'compressed) (file-options compressed) (file-options))
(if (eq? bmode 'unbuffered) (buffer-mode none) (buffer-mode block))
(current-transcoder))
(case (car o)
[(compressed uncompressed)
(check-option who zmode (car o))
(loop (cdr o) (car o) bmode)]
[(buffered unbuffered)
(check-option who bmode (car o))
(loop (cdr o) zmode (car o))]
[else ($oops who "invalid option ~s" (car o))])))))
(set-who! #(r6rs: open-input-file)
(lambda (s) (oif who s '())))
(set-who! open-input-file
(case-lambda
[(s) (oif who s '())]
[(s o) (oif who s o)]))
(let ()
(define (cwif who s f o)
(unless (procedure? f)
($oops 'call-with-input-file "~s is not a procedure" f))
(let ([p (oif 'call-with-input-file s o)])
(call-with-values
(lambda () (f p))
(lambda args (close-input-port p) (apply values args)))))
(set-who! #(r6rs: call-with-input-file)
(lambda (s f) (cwif who s f '())))
(set-who! call-with-input-file
(case-lambda
[(s f) (cwif who s f '())]
[(s f o) (cwif who s f o)])))
(let ()
(define (wiff who s f o)
(unless (procedure? f)
($oops 'with-input-from-file "~s is not a procedure" f))
(let ([p (oif 'with-input-from-file s o)])
(call-with-values
(lambda () (parameterize ([current-input-port p]) (f)))
(lambda v (close-input-port p) (apply values v)))))
(set-who! #(r6rs: with-input-from-file)
(lambda (s f) (wiff who s f '())))
(set-who! with-input-from-file
(case-lambda
[(s f) (wiff who s f '())]
[(s f o) (wiff who s f o)]))))
)
;; open-bytevector-input-port
(let ()
;; port-info stores whether to claim it is nonblocking or not
(define $bytevector-input-handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
#t)]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(if (port-input-empty? p)
(eof-object)
(bytevector-u8-ref (binary-port-input-buffer p)
(binary-port-input-index p))))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(if (eof-object? x)
;; We don't set port-eof b/c #!eof only comes at end anyway
(unless (port-input-empty? p) (unget-error who p x))
(let ([index (binary-port-input-index p)])
(when (eq? 0 index) (unget-error who p x))
(set-binary-port-input-index! p (fx1- index)))))]
[get
(lambda (who p)
(assert-not-closed who p)
(if (port-input-empty? p)
(eof-object)
(let ([index (binary-port-input-index p)])
(set-binary-port-input-index! p (fx1+ index))
(bytevector-u8-ref (binary-port-input-buffer p) index))))]
[get-some
(lambda (who p bv start count)
(assert-not-closed who p)
(let ([port-count (binary-port-input-count p)])
(if (eq? 0 port-count)
(eof-object)
(let ([index (binary-port-input-index p)]
[count (fxmin count port-count)])
(bytevector-copy! (binary-port-input-buffer p) index
bv start count)
(set-binary-port-input-index! p (fx+ index count))
count))))]
[clear-input
(lambda (who p)
(assert-not-closed who p))]
[put #f]
[put-some #f]
[flush #f]
[clear-output #f]
[close-port
(lambda (who p)
(unless (port-closed? p)
(mark-port-closed! p)
(set-binary-port-input-size! p 0)))]
[port-position
(lambda (who p)
(assert-not-closed who p)
(binary-port-input-index p))]
[set-port-position!
(lambda (who p x)
(assert-not-closed who p)
(unless (and (fixnum? x) (not ($fxu< (binary-port-input-size p) x)))
(if (or (and (fixnum? x) (fx>= x 0)) (and (bignum? x) (>= x 0)))
(position-oops who p x "out of range")
($oops who "~s is not a valid position" x)))
(set-binary-port-input-index! p x))]
[port-length
(lambda (who p)
(assert-not-closed who p)
(bytevector-length (binary-port-input-buffer p)))]
[set-port-length! #f]
[port-nonblocking?
(lambda (who p)
(assert-not-closed who p)
($port-info p))]
[set-port-nonblocking!
(lambda (who p x)
(assert-not-closed who p)
($set-port-info! p x))]))
(define open-binary-bytevector-input-port
(lambda (bv)
(define who 'open-bytevector-input-port)
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(let ([p ($make-binary-input-port "bytevector" $bytevector-input-handler bv #f)])
($set-port-flags! p (constant port-flag-block-buffered))
p)))
(set-who! open-bytevector-input-port
(case-lambda
[(bv) (open-binary-bytevector-input-port bv)]
[(bv maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([binary-port (open-binary-bytevector-input-port bv)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))]))
)
;; open-string-input-port
(let ()
;; port-info stores whether to claim it is nonblocking or not
(define $string-input-handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
#t)]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(if (port-input-empty? p)
(eof-object)
(string-ref (textual-port-input-buffer p)
(textual-port-input-index p))))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(if (eof-object? x)
;; We don't set port-eof b/c #!eof only comes at end anyway
(unless (port-input-empty? p) (unget-error who p x))
(let ([index (textual-port-input-index p)])
(when (eq? 0 index) (unget-error who p x))
(set-textual-port-input-index! p (fx1- index)))))]
[get
(lambda (who p)
(assert-not-closed who p)
(if (port-input-empty? p)
(eof-object)
(let ([index (textual-port-input-index p)])
(set-textual-port-input-index! p (fx1+ index))
(string-ref (textual-port-input-buffer p) index))))]
[get-some
(lambda (who p st start count)
(assert-not-closed who p)
(let ([port-count (textual-port-input-count p)])
(if (eq? 0 port-count)
(eof-object)
(let ([index (textual-port-input-index p)]
[count (fxmin count port-count)])
(string-copy! (textual-port-input-buffer p) index
st start count)
(set-textual-port-input-index! p (fx+ index count))
count))))]
[clear-input
(lambda (who p)
(assert-not-closed who p))]
[put #f]
[put-some #f]
[flush #f]
[clear-output #f]
[close-port
(lambda (who p)
(unless (port-closed? p)
(mark-port-closed! p)
(set-textual-port-input-size! p 0)))]
[port-position
(lambda (who p)
(assert-not-closed who p)
(textual-port-input-index p))]
[set-port-position!
(lambda (who p x)
(assert-not-closed who p)
(unless (and (fixnum? x) (not ($fxu< (textual-port-input-size p) x)))
(if (or (and (fixnum? x) (fx>= x 0)) (and (bignum? x) (>= x 0)))
(position-oops who p x "out of range")
($oops who "~s is not a valid position" x)))
(set-textual-port-input-index! p x))]
[port-length
(lambda (who p)
(assert-not-closed who p)
(string-length (textual-port-input-buffer p)))]
[set-port-length! #f]
[port-nonblocking?
(lambda (who p)
(assert-not-closed who p)
($port-info p))]
[set-port-nonblocking!
(lambda (who p x)
(assert-not-closed who p)
($set-port-info! p x))]))
(define (osip who str)
(unless (string? str)
($oops who "~s is not a string" str))
(let ([p ($make-textual-input-port "string" $string-input-handler str #f)])
($set-port-flags! p (constant port-flag-block-buffered))
($set-port-flags! p (constant port-flag-char-positions))
p))
(set-who! open-string-input-port
(lambda (str)
(osip who str)))
(set-who! open-input-string
(lambda (str)
(osip who str)))
)
;; standard-input-port in open-binary-file-input-port section
;; current-input-port in prims.ss
(set-who! make-custom-binary-input-port
(lambda (id read! get-position set-position! close)
(unless (string? id) ($oops who "~s is not a string" id))
(unless (procedure? read!) ($oops who "~s is not a procedure" read!))
(unless (or (not get-position) (procedure? get-position))
($oops who "~s is not a procedure or #f" get-position))
(unless (or (not set-position!) (procedure? set-position!))
($oops who "~s is not a procedure or #f" set-position!))
(unless (or (not close) (procedure? close))
($oops who "~s is not a procedure or #f" close))
(let ([handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
(or (not (port-input-empty? p))
(port-flag-eof-set? p)
(read-oops who p "cannot determine ready status")))]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-lookahead who p read!))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(binary-custom-port-unget who p x))]
[get
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-get who p read!))]
[get-some
(lambda (who p bv start count)
(assert-not-closed who p)
(binary-custom-port-get-some who p read! bv start count))]
[clear-input
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-clear-input who p))]
[put #f]
[put-some #f]
[flush #f]
[clear-output #f]
[close-port
(lambda (who p)
(unless (port-closed? p)
(binary-custom-port-close-port who p close)))]
[port-position
(and get-position
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-port-position in who p get-position)))]
[set-port-position!
(and set-position!
(lambda (who p x)
(unless (or (and (fixnum? x) (fx>= x 0)) (and (bignum? x) (>= x 0)))
($oops who "~s is not a valid position" x))
(assert-not-closed who p)
(set-binary-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f)
(set-position! x)))]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f])])
(let ([p ($make-binary-input-port id handler
(make-bytevector (custom-port-buffer-size))
#f)])
($set-port-flags! p (constant port-flag-block-buffered))
(set-binary-port-input-size! p 0)
p))))
(set-who! make-custom-textual-input-port
(lambda (id read! get-position set-position! close)
(unless (string? id) ($oops who "~s is not a string" id))
(unless (procedure? read!) ($oops who "~s is not a procedure" read!))
(unless (or (not get-position) (procedure? get-position))
($oops who "~s is not a procedure or #f" get-position))
(unless (or (not set-position!) (procedure? set-position!))
($oops who "~s is not a procedure or #f" set-position!))
(unless (or (not close) (procedure? close))
($oops who "~s is not a procedure or #f" close))
(let ([handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
(or (not (port-input-empty? p))
(port-flag-eof-set? p)
(read-oops who p "cannot determine ready status")))]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-lookahead who p read!))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(textual-custom-port-unget who p x))]
[get
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-get who p read!))]
[get-some
(lambda (who p str start count)
(assert-not-closed who p)
(textual-custom-port-get-some who p read! str start count))]
[clear-input
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-clear-input who p))]
[put #f]
[put-some #f]
[flush #f]
[clear-output #f]
[close-port
(lambda (who p)
(unless (port-closed? p)
(textual-custom-port-close-port who p close)))]
[port-position
(and get-position
(lambda (who p)
(assert-not-closed who p)
(unless (port-input-empty? p)
(position-warning who
"cannot determine accurate position after read on ~s"
p))
(get-position)))]
[set-port-position!
(and set-position!
(lambda (who p x)
(assert-not-closed who p)
(set-textual-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f)
(set-position! x)))]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f])])
(let ([p ($make-textual-input-port id handler
(make-string (custom-port-buffer-size))
#f)])
($set-port-flags! p (constant port-flag-block-buffered))
(set-textual-port-input-size! p 0)
p))))
;;;; 8.2.8 Binary input
;; get-u8 in prims.ss
;; lookahead-u8 in prims.ss
;; unget-u8 in prims.ss
;; get-bytevector! :: port * bv * start * max -> count TODO(not R6RS)
(let ()
;; This helper handles all the looping for the following functions
(define (get-bytevector-min-max who p bv start min max)
(if (eq? 0 max)
0
(let ([get-some (port-handler-get-some ($port-handler p))])
;; Loop invariant:
;; next = next spot to fill in the bytevector
;; min = minimum left to read
;; max = maximum left to read
(let loop ([next start]
[min min]
[max max])
(let ([n (get-some who p bv next max)])
(if (eof-object? n)
(if (eq? start next)
(eof-object) ;; We couldn't even read one byte
(begin ;; Got some but got #!eof before full
(call-port-handler unget who p (eof-object)) ;; Put the #!eof back
(fx- next start))) ;; Return our count
(let ([min (fx- min n)]
[next (fx+ next n)])
(if (fx<= min 0)
(fx- next start) ;; We got enough to stop
(loop next min (fx- max n))))))))))
(define (append-blocks size block-size block blocks)
(let ([buffer (#2%make-bytevector size)])
(let loop ([block-size block-size] [block block] [blocks blocks] [end size])
(let ([end (fx- end block-size)])
(bytevector-copy! block 0 buffer end block-size)
(if (null? blocks)
buffer
(loop (caar blocks) (cdar blocks) (cdr blocks) end))))))
(set-who! get-bytevector-n
(lambda (binary-input-port count)
(unless (and (input-port? binary-input-port) (binary-port? binary-input-port))
($oops who "~s is not a binary input port" binary-input-port))
(unless (and (fixnum? count) (fx>= count 0))
($oops who "~s is not a nonnegative fixnum" count))
(let ([buffer-size (file-buffer-size)])
(if (not ($fxu< buffer-size count))
(let ([bv (make-bytevector count)])
(let ([n (get-bytevector-min-max
who binary-input-port bv 0 count count)])
(if (eof-object? n) n (bytevector-truncate! bv n))))
(let ([get-some (port-handler-get-some ($port-handler binary-input-port))])
(let loop ([count count]
[size 0]
[next-block-index 0]
[next-block (make-bytevector buffer-size)]
[blocks '()])
(let ([next-size (get-some who binary-input-port
next-block next-block-index
(fxmin count (fx- buffer-size next-block-index)))])
(if (or (eof-object? next-size) (eq? next-size 0))
(if (eqv? size 0)
(if (eof-object? next-size) (eof-object) #vu8())
(append-blocks size next-block-index next-block blocks))
(let ([count (fx- count next-size)]
[size (fx+ size next-size)]
[next-block-index (fx+ next-block-index next-size)])
(if (eqv? count 0)
(append-blocks size next-block-index next-block blocks)
(if (fx>= next-block-index (fxquotient buffer-size 2))
(loop count size 0
(make-bytevector buffer-size)
(cons (cons next-block-index next-block) blocks))
(loop count size next-block-index next-block blocks))))))))))))
(set-who! get-bytevector-n!
(lambda (binary-input-port bv start count)
(unless (and (input-port? binary-input-port) (binary-port? binary-input-port))
($oops who "~s is not a binary input port" binary-input-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless (and (fixnum? start) (fx>= start 0))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx>= count 0))
($oops who "invalid count ~s" count))
(unless (fx<= count (fx- (bytevector-length bv) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count bv))
(get-bytevector-min-max who binary-input-port bv start count count)))
(set-who! get-bytevector-some
(lambda (binary-input-port)
(let ([buffer-size (file-buffer-size)])
(unless (and (input-port? binary-input-port) (binary-port? binary-input-port))
($oops who "~s is not a binary input port" binary-input-port))
(let ([bv (make-bytevector buffer-size)])
(let ([n (get-bytevector-min-max who binary-input-port bv 0 0 buffer-size)])
(if (eof-object? n)
(eof-object)
(bytevector-truncate! bv n)))))))
(set-who! get-bytevector-some!
(lambda (binary-input-port bv start count)
(unless (and (input-port? binary-input-port) (binary-port? binary-input-port))
($oops who "~s is not a binary input port" binary-input-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless (and (fixnum? start) (fx>= start 0))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx>= count 0))
($oops who "invalid count ~s" count))
(unless (fx<= count (fx- (bytevector-length bv) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count bv))
(get-bytevector-min-max who binary-input-port bv start 0 count)))
(set-who! get-bytevector-all
(lambda (binary-input-port)
(unless (and (input-port? binary-input-port) (binary-port? binary-input-port))
($oops who "~s is not a binary input port" binary-input-port))
(let ([buffer-size (file-buffer-size)])
(let ([get-some (port-handler-get-some ($port-handler binary-input-port))])
(let loop ([size 0]
[next-block-index 0]
[next-block (make-bytevector buffer-size)]
[blocks '()])
(let ([next-size (get-some who binary-input-port
next-block next-block-index
(fx- buffer-size next-block-index))])
(if (eof-object? next-size)
(if (eq? size 0)
(eof-object)
(append-blocks size next-block-index next-block blocks))
(let ([size (fx+ size next-size)]
[next-block-index (fx+ next-block-index next-size)])
(if (fx>= next-block-index (fxquotient buffer-size 2))
(loop size 0
(make-bytevector buffer-size)
(cons (cons next-block-index next-block) blocks))
(loop size next-block-index next-block blocks))))))))))
)
;;;; 8.2.9 Textual input
;; get-char in prims.ss
;; lookahead-char in prims.ss
(let ()
;; TODO: this code is identical to get-bytevector-min-max
;; This helper handles all the looping for the following functions
(define (get-string-min-max who p bv start min max)
(if (eq? 0 max)
0
(let ([get-some (port-handler-get-some ($port-handler p))])
;; Loop invariant:
;; next = next spot to fill in the bytevector
;; min = minimum left to read
;; max = maximum left to read
(let loop ([next start]
[min min]
[max max])
(let ([n (get-some who p bv next max)])
(if (eof-object? n)
(if (eq? start next)
(eof-object) ;; We couldn't even read one byte
(begin ;; Got some but got #!eof before full
(call-port-handler unget who p (eof-object)) ;; Put the #!eof back
(fx- next start))) ;; Return our count
(let ([min (fx- min n)]
[next (fx+ next n)])
(if (fx<= min 0)
(fx- next start) ;; We got enough to stop
(loop next min (fx- max n))))))))))
(define (append-blocks size block-size block blocks)
(let ([buffer (#2%make-string size)])
(let loop ([block-size block-size] [block block] [blocks blocks] [end size])
(let ([end (fx- end block-size)])
(string-copy! block 0 buffer end block-size)
(if (null? blocks)
buffer
(loop (caar blocks) (cdar blocks) (cdr blocks) end))))))
(define $get-string-all
(lambda (who textual-input-port)
(let ([buffer-size (file-buffer-size)])
(let ([get-some (port-handler-get-some ($port-handler textual-input-port))])
(let loop ([size 0]
[next-block-index 0]
[next-block (make-string buffer-size)]
[blocks '()])
(let ([next-size (get-some who textual-input-port
next-block next-block-index
(fx- buffer-size next-block-index))])
(if (eof-object? next-size)
(if (eq? size 0)
(eof-object)
(append-blocks size next-block-index next-block blocks))
(let ([size (fx+ size next-size)]
[next-block-index (fx+ next-block-index next-size)])
(if (fx>= next-block-index (fxquotient buffer-size 2))
(loop size 0
(make-string buffer-size)
(cons (cons next-block-index next-block) blocks))
(loop size next-block-index next-block blocks))))))))))
(set-who! get-string-n
(lambda (textual-input-port count)
(unless (and (input-port? textual-input-port) (textual-port? textual-input-port))
($oops who "~s is not a textual input port" textual-input-port))
(unless (and (fixnum? count) (fx>= count 0))
($oops who "~s is not a nonnegative fixnum" count))
(let ([buffer-size (file-buffer-size)])
(if (not ($fxu< buffer-size count))
(let ([st (make-string count)])
(let ([n (get-string-min-max
who textual-input-port st 0 count count)])
(if (eof-object? n) n (string-truncate! st n))))
(let ([get-some (port-handler-get-some ($port-handler textual-input-port))])
(let loop ([count count]
[size 0]
[next-block-index 0]
[next-block (make-string buffer-size)]
[blocks '()])
(let ([next-size (get-some who textual-input-port
next-block next-block-index
(fxmin count (fx- buffer-size next-block-index)))])
(if (or (eof-object? next-size) (eq? next-size 0))
(if (eqv? size 0)
(if (eof-object? next-size) (eof-object) "")
(append-blocks size next-block-index next-block blocks))
(let ([count (fx- count next-size)]
[size (fx+ size next-size)]
[next-block-index (fx+ next-block-index next-size)])
(if (eqv? count 0)
(append-blocks size next-block-index next-block blocks)
(if (fx>= next-block-index (fxquotient buffer-size 2))
(loop count size 0
(make-string buffer-size)
(cons (cons next-block-index next-block) blocks))
(loop count size next-block-index next-block blocks))))))))))))
(set-who! get-string-n!
(lambda (textual-input-port st start count)
(unless (and (input-port? textual-input-port) (textual-port? textual-input-port))
($oops who "~s is not a textual input port" textual-input-port))
(unless (string? st)
($oops who "~s is not a string" st))
(unless (and (fixnum? start) (fx>= start 0))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx>= count 0))
($oops who "invalid count ~s" count))
(unless (fx<= count (fx- (string-length st) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count st))
(get-string-min-max who textual-input-port st start count count)))
(set-who! get-string-some
(lambda (textual-input-port)
(unless (and (input-port? textual-input-port) (textual-port? textual-input-port))
($oops who "~s is not a textual input port" textual-input-port))
(let ([buffer-size (file-buffer-size)])
(let ([st (make-string buffer-size)])
(let ([n (get-string-min-max who textual-input-port st 0 0 buffer-size)])
(if (eof-object? n)
(eof-object)
(string-truncate! st n)))))))
(set-who! get-string-some!
(lambda (textual-input-port st start count)
(unless (and (input-port? textual-input-port) (textual-port? textual-input-port))
($oops who "~s is not a textual input port" textual-input-port))
(unless (string? st)
($oops who "~s is not a string" st))
(unless (and (fixnum? start) (fx>= start 0))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx>= count 0))
($oops who "invalid count ~s" count))
(unless (fx<= count (fx- (string-length st) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count st))
(get-string-min-max who textual-input-port st start 0 count)))
(set-who! get-string-all
(lambda (textual-input-port)
(unless (and (input-port? textual-input-port) (textual-port? textual-input-port))
($oops who "~s is not a textual input port" textual-input-port))
($get-string-all who textual-input-port)))
(set-who! bytevector->string
(lambda (bv tx)
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless ($transcoder? tx)
($oops who "~s is not a transcoder" tx))
(let ([str ($get-string-all who (open-bytevector-input-port bv tx))])
(if (eof-object? str) "" str))))
)
(set-who! get-line
(lambda (tp)
(unless (and (input-port? tp) (textual-port? tp))
($oops who "~s is not a textual input port" tp))
(let f ([n 0])
(let ([c (get-char tp)])
(cond
[(eof-object? c) (if (fx= n 0) c (begin (unget-char tp c) (make-string n)))]
[(char=? c #\newline) (make-string n)]
[else (let ([s (f (fx+ n 1))]) (string-set! s n c) s)])))))
;; get-datum in read.ss
;;;; 8.2.10 Output ports
;; output-port? in prims.ss
(let ()
(define who 'flush-output-port)
(define flush-help
(lambda (output-port)
(call-port-handler flush who output-port)))
(define flush-check-help
(lambda (output-port)
(unless (output-port? output-port)
($oops who "~s is not an output port" output-port))
(flush-help output-port)))
(set! flush-output-port
(case-lambda
[() (flush-help (current-output-port))]
[(output-port) (flush-check-help output-port)]))
(set! r6rs:flush-output-port
(rec flush-output-port
(lambda (output-port)
(flush-check-help output-port)))))
; input-port-buffer-mode isn't required by r6rs but would be essentially
; the same code. if anything, it would be even more useless.
(set-who! output-port-buffer-mode
(lambda (output-port)
(unless (output-port? output-port)
($oops who "~s is not an output port" output-port))
(cond
[($port-flags-set? output-port (constant port-flag-block-buffered))
(buffer-mode block)]
[($port-flags-set? output-port (constant port-flag-line-buffered))
(buffer-mode line)]
[else (buffer-mode none)])))
;; open-file-output-port
(let ()
(define open-binary-file-output-port
(lambda (who filename options perms b-mode)
(let ([no-create (enum-set-subset? (file-options no-create) options)]
[no-fail (enum-set-subset? (file-options no-fail) options)]
[no-truncate (enum-set-subset? (file-options no-truncate) options)]
[append (enum-set-subset? (file-options append) options)]
[lock (enum-set-subset? (file-options exclusive) options)]
[replace (enum-set-subset? (file-options replace) options)]
[compressed (enum-set-subset? (file-options compressed) options)])
(when (and compressed lock)
($oops who "exclusive option is not supported with compress option"))
(when-feature windows
(unless-feature pthreads
; try to work around windows file open semantics by trying
; to close any open ports to the file if we cannot delete it
; without doing so.
(when replace
(delete-file filename #f)
(when (file-exists? filename)
(collect (collect-maximum-generation))))))
(let ([fd (critical-section
($open-output-fd filename perms
no-create no-fail no-truncate
append lock replace compressed))])
(when (pair? fd) (open-oops who filename options fd))
(open-binary-fd-output-port who filename fd #t b-mode lock compressed)))))
(define help-open-file-output-port
(lambda (who filename options perms b-mode maybe-transcoder)
(let ([bp (open-binary-file-output-port who filename options perms b-mode)])
(if maybe-transcoder
(transcoded-port bp maybe-transcoder)
bp))))
(define open-binary-standard-output-port
(lambda (who fd name b-mode)
(unless (buffer-mode? b-mode)
($oops who "~s is not a valid buffer mode" b-mode))
(open-binary-fd-output-port who name (make-fd fd) ($fd-regular? fd) b-mode #f #f)))
(set-who! open-file-output-port
(rec open-file-output-port
(case-lambda
[(filename) (open-file-output-port filename (file-options))]
[(filename options) (open-file-output-port filename options (buffer-mode block))]
[(filename options b-mode) (open-file-output-port filename options b-mode #f)]
[(filename options b-mode maybe-transcoder)
(unless (string? filename) ($oops who "~s is not a string" filename))
(unless (and (enum-set? options) (enum-set-subset? options $file-options))
($oops who "~s is not a file-options object" options))
(unless (buffer-mode? b-mode)
($oops who "~s is not a valid buffer mode" b-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(help-open-file-output-port who filename options
(extract-permission-mask options) b-mode maybe-transcoder)])))
(set! $open-file-output-port
(rec $open-file-output-port
(case-lambda
[(who filename) ($open-file-output-port who filename (file-options))]
[(who filename options) ($open-file-output-port who filename options (buffer-mode block))]
[(who filename options b-mode) ($open-file-output-port who filename options b-mode #f)]
[(who filename options b-mode maybe-transcoder)
(unless (string? filename) ($oops who "~s is not a string" filename))
(unless (and (enum-set? options) (enum-set-subset? options $file-options))
($oops who "~s is not a file-options object" options))
(unless (buffer-mode? b-mode)
($oops who "~s is not a valid buffer mode" b-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(help-open-file-output-port who filename options
(extract-permission-mask options) b-mode maybe-transcoder)])))
(set-who! open-fd-output-port
(case-lambda
[(fd)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(open-binary-fd-output-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) (buffer-mode block) #f #f)]
[(fd buffer-mode)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(unless (buffer-mode? buffer-mode)
($oops who "~s is not a buffer mode" buffer-mode))
(open-binary-fd-output-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) buffer-mode #f #f)]
[(fd buffer-mode maybe-transcoder)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(unless (buffer-mode? buffer-mode)
($oops who "~s is not a buffer mode" buffer-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([bp (open-binary-fd-output-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) buffer-mode #f #f)])
(if maybe-transcoder
(transcoded-port bp maybe-transcoder)
bp))]))
(set-who! standard-output-port
(case-lambda
[() (open-binary-standard-output-port who 1 "stdout" (buffer-mode line))]
[(b-mode) (open-binary-standard-output-port who 1 "stdout" b-mode)]
[(b-mode maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([binary-port (open-binary-standard-output-port who 1 "stdout" b-mode)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))]))
(set-who! r6rs:standard-output-port
(rec standard-output-port
(lambda ()
(open-binary-standard-output-port who 1 "stdout" (buffer-mode line)))))
(set-who! standard-error-port
(case-lambda
[() (open-binary-standard-output-port who 2 "stderr" (buffer-mode none))]
[(b-mode) (open-binary-standard-output-port who 2 "stderr" b-mode)]
[(b-mode maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([binary-port (open-binary-standard-output-port who 2 "stderr" b-mode)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))]))
(set-who! r6rs:standard-error-port
(rec standard-error-port
(lambda ()
(open-binary-standard-output-port who 2 "stderr" (buffer-mode none)))))
; simple i/o routines here to share helpers
(let ()
(define (oof who s o)
(unless (string? s) ($oops who "~s is not a string" s))
(let ([o (if (list? o) o (list o))])
(let loop ([o o] [ifexists #f] [mode #o666] [zmode #f] [xmode #f] [bmode #f])
(if (null? o)
(help-open-file-output-port who s
(enum-set-union
(enum-set-union
(case ifexists
[(error) (file-options)]
[(truncate) (file-options no-fail)]
[(replace) (file-options no-fail no-truncate replace)]
[(append) (file-options append no-fail no-truncate)]
[else (file-options)])
(if (eq? zmode 'compressed) (file-options compressed) (file-options)))
(if (eq? xmode 'exclusive) (file-options exclusive) (file-options)))
mode
(if (eq? bmode 'unbuffered) (buffer-mode none) (buffer-mode block))
(current-transcoder))
(case (car o)
[(error truncate replace append)
(check-option who ifexists (car o))
(loop (cdr o) (car o) mode zmode xmode bmode)]
[(compressed uncompressed)
(check-option who zmode (car o))
(loop (cdr o) ifexists mode (car o) xmode bmode)]
[(buffered unbuffered)
(check-option who bmode (car o))
(loop (cdr o) ifexists mode zmode xmode (car o))]
[(exclusive nonexclusive)
(check-option who xmode (car o))
(loop (cdr o) ifexists mode zmode (car o) bmode)]
[(mode)
(if (null? (cdr o))
($oops who "mode option requires an argument")
(let ([mode (cadr o)])
(if (and (fixnum? mode) (fx>= mode 0))
(loop (cddr o) ifexists mode zmode xmode bmode)
($oops who "mode argument must be a nonnegative fixnum"))))]
[else ($oops who "invalid option ~s" (car o))])))))
(set-who! #(r6rs: open-output-file)
(lambda (s) (oof who s '())))
(set-who! open-output-file
(case-lambda
[(s) (oof who s '())]
[(s o) (oof who s o)]))
(let ()
(define (cwof who s f o)
(unless (procedure? f)
($oops who "~s is not a procedure" f))
(let ([p (oof who s o)])
(call-with-values
(lambda () (f p))
(lambda args
(close-output-port p)
(apply values args)))))
(set-who! #(r6rs: call-with-output-file)
(lambda (s f) (cwof who s f '())))
(set-who! call-with-output-file
(case-lambda
[(s f) (cwof who s f '())]
[(s f o) (cwof who s f o)])))
(let ()
(define (wotf who s f o)
(unless (procedure? f)
($oops who "~s is not a procedure" f))
(let ([p (oof who s o)])
(call-with-values
(lambda () (parameterize ([current-output-port p]) (f)))
(lambda v
(close-output-port p)
(apply values v)))))
(set-who! #(r6rs: with-output-to-file)
(lambda (s f) (wotf who s f '())))
(set-who! with-output-to-file
(case-lambda
[(s f) (wotf who s f '())]
[(s f o) (wotf who s f o)]))))
)
;; open-bytevector-output-port
(let ()
;; if info-index != index, there was put/put-some after last set-pos
;; and (max info-length index) is true length
;; if info-index == index, there was set-pos after last put/put-some
;; and info-length is true length
;; Invarient: info-index <= index
;; Invarient: size = (max length index)
;; Invarient: if no put/put-some after last set-pos/set-length,
;; then info-index = index and true length = info-length
;; Invarient: if put/put-some after last set-pos/set-length,
;; then info-index < index and true length = max info-length index
;; It is always safe to increment index when count != 0
;; It is always safe to write at index when count != 0
;; Index always contains the current position
;; The only operation that needs to decrement index is set-position
;; which needs to set info-index anyway
(define-record-type bytevector-output-port-info
(nongenerative)
(opaque #t)
(sealed #t)
(fields
(mutable index)
(mutable length)
(mutable nonblocking)))
;; NOTE: leaves index at 0, callers must reset index if needed
(define (extend-buffer p count)
(let ([old-size (binary-port-output-size p)]
[old-buffer (binary-port-output-buffer p)]
[old-index (binary-port-output-index p)])
(let* ([new-length (fxmax bytevector-buffer-length
(fx* 2 (fx+ old-size count)))]
[new-buffer (make-bytevector new-length)])
(bytevector-copy! old-buffer 0 new-buffer 0
(fxmin (bytevector-length old-buffer) old-size))
(set-binary-port-output-buffer! p new-buffer))))
(define port-length
(lambda (who p)
(let ([info ($port-info p)]
[index (binary-port-output-index p)])
(let ([info-index (bytevector-output-port-info-index info)]
[info-length (bytevector-output-port-info-length info)])
(if (eq? index info-index)
info-length ;; last op was set-pos
(max index info-length)))))) ;; last op was put
(define $bytevector-output-handler
(make-port-handler
[ready? #f]
[lookahead #f]
[unget #f]
[get #f]
[get-some #f]
[clear-input #f]
[put
(lambda (who p x)
(assert-not-closed who p)
(let ([index (binary-port-output-index p)])
(when (port-output-full? p) (extend-buffer p 0))
(bytevector-u8-set! (binary-port-output-buffer p) index x)
(set-binary-port-output-index! p (fx1+ index))))]
[put-some
(lambda (who p bv start count)
(assert-not-closed who p)
(let ([index (binary-port-output-index p)])
(when ($fxu< (binary-port-output-count p) count) (extend-buffer p count))
(bytevector-copy! bv start
(binary-port-output-buffer p) index count)
(set-binary-port-output-index! p (fx+ index count)))
count)]
[flush ; no-op on bytevector output ports
(lambda (who p)
(assert-not-closed who p))]
[clear-output ; no-op on bytevector output ports
(lambda (who p)
(assert-not-closed who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
; sync info-index for possible post-close extraction
(let ([info ($port-info p)] [index (binary-port-output-index p)])
(unless (eq? index (bytevector-output-port-info-index info))
(bytevector-output-port-info-length-set! info
(fxmax index (bytevector-output-port-info-length info)))))
(mark-port-closed! p)
(set-binary-port-output-size! p 0)))]
[port-position
(lambda (who p)
(assert-not-closed who p)
(binary-port-output-index p))]
[set-port-position!
(lambda (who p pos)
(assert-not-closed who p)
(unless (and (fixnum? pos) (fx>= pos 0))
(if (and (bignum? pos) (>= pos 0))
(position-oops who p pos "out of range")
($oops who "~s is not a valid position" pos)))
(let ([info ($port-info p)]
[index (binary-port-output-index p)])
; unless last op was set-pos, save the true length
(unless (eq? index (bytevector-output-port-info-index info))
(bytevector-output-port-info-length-set! info
(fxmax index (bytevector-output-port-info-length info))))
(set-binary-port-output-size! p
(fxmax pos (fx1- (bytevector-length (binary-port-output-buffer p)))))
(set-binary-port-output-index! p pos)
(bytevector-output-port-info-index-set! info pos)))]
[port-length
(lambda (who p)
(assert-not-closed who p)
(port-length who p))]
[set-port-length!
(lambda (who p pos)
(unless (and (fixnum? pos) (fx>= pos 0))
(if (and (bignum? pos) (>= pos 0))
(position-oops who p pos "out of range")
($oops who "~s is not a valid length" pos)))
(assert-not-closed who p)
(let ([info ($port-info p)]
[index (binary-port-output-index p)]
[size (binary-port-output-size p)])
;; ensure the bytevector is long enough
(let ([buflen-1 (fx1- (bytevector-length (binary-port-output-buffer p)))])
(when ($fxu< buflen-1 pos)
(extend-buffer p (fx- pos buflen-1))
(set-binary-port-output-index! p index)))
;; make it look like a set-pos was done last
;; (i.e. index might be beyond true length)
(bytevector-output-port-info-index-set! info index)
;; set the true length
(bytevector-output-port-info-length-set! info pos)))]
[port-nonblocking?
(lambda (who p)
(assert-not-closed who p)
(bytevector-output-port-info-nonblocking ($port-info p)))]
[set-port-nonblocking!
(lambda (who p x)
(assert-not-closed who p)
(bytevector-output-port-info-nonblocking-set! ($port-info p) x))]))
(define extractor
(lambda (p)
(let ([old-buffer
(bytevector-truncate!
(binary-port-output-buffer p)
(port-length #f p))])
(set-binary-port-output-buffer! p #vu8())
(let ([info ($port-info p)])
(bytevector-output-port-info-index-set! info 0)
(bytevector-output-port-info-length-set! info 0))
old-buffer)))
(define open-binary-bytevector-output-port
(lambda ()
(let ([p ($make-binary-output-port "bytevector"
$bytevector-output-handler
#vu8()
(make-bytevector-output-port-info 0 0 #f))])
($set-port-flags! p (constant port-flag-block-buffered))
(values
p
(lambda ()
(let ([info ($port-info p)])
(if (bytevector-output-port-info? info)
(extractor p)
; the port must have been transcoded
(begin
(flush-output-port info)
(extractor (codec-info-bp ($port-info info)))))))))))
(set-who! open-bytevector-output-port
(case-lambda
[() (open-binary-bytevector-output-port)]
[(maybe-transcoder)
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let-values ([(binary-port extractor)
(open-binary-bytevector-output-port)])
(values
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port)
extractor))]))
)
;; open-bytevector-list-output-port
(let ()
(define-record-type bv-list-op-info
(nongenerative)
(sealed #t)
(fields
(mutable nonblocking)
(mutable bv*)))
; allocate in chunk-size chunks
(define chunk-size 4096)
(define (extend-buffer p)
(let ([bv (binary-port-output-buffer p)])
(unless (eqv? bv #vu8())
(let ([info ($port-info p)])
(bv-list-op-info-bv*-set! info
(cons bv (bv-list-op-info-bv* info))))))
(set-binary-port-output-buffer! p (make-bytevector chunk-size)))
(define $bytevector-list-output-handler
(make-port-handler
[ready? #f]
[lookahead #f]
[unget #f]
[get #f]
[get-some #f]
[clear-input #f]
[put
(lambda (who p x)
(assert-not-closed who p)
(when (port-output-full? p) (extend-buffer p))
(let ([index (binary-port-output-index p)])
(bytevector-u8-set! (binary-port-output-buffer p) index x)
(set-binary-port-output-index! p (fx1+ index))))]
[put-some
(lambda (who p bv start count)
(assert-not-closed who p)
(when (port-output-full? p) (extend-buffer p))
(let ([count (fxmin count (binary-port-output-count p))]
[index (binary-port-output-index p)])
(bytevector-copy! bv start (binary-port-output-buffer p) index count)
(set-binary-port-output-index! p (fx+ index count))
count))]
[flush ; no-op on bytevector output ports
(lambda (who p)
(assert-not-closed who p))]
[clear-output ; no-op on bytevector output ports
(lambda (who p)
(assert-not-closed who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
; sync info-index for possible post-close extraction
#;(let ([old-buffer (bytevector-truncate!
(binary-port-output-buffer p)
(binary-port-output-index p))]
[bv* (bv-list-op-info-bv* info)])
(bv-list-op-info-size-set! info
(fx+ (bytevector-length old-buffer)
(fx* (length bv*) chunk-size)))
(bv-list-op-info-bv*-set! info
(reverse (if (eq? old-buffer #vu8())
bv*
(cons old-buffer bv*)))))
(mark-port-closed! p)
(set-binary-port-output-size! p 0)))]
[port-position
(lambda (who p)
(assert-not-closed who p)
(fx+ (binary-port-output-index p)
(fx* (length (bv-list-op-info-bv* ($port-info p))) chunk-size)))]
[set-port-position! #f]
[port-length
(lambda (who p)
(assert-not-closed who p)
(fx+ (binary-port-output-index p)
(fx* (length (bv-list-op-info-bv* ($port-info p))) chunk-size)))]
[set-port-length! #f]
[port-nonblocking?
(lambda (who p)
(assert-not-closed who p)
(bv-list-op-info-nonblocking ($port-info p)))]
[set-port-nonblocking!
(lambda (who p x)
(assert-not-closed who p)
(bv-list-op-info-nonblocking-set! ($port-info p) x))]))
(define extractor
(lambda (p)
(let ([info ($port-info p)])
(let ([bv (bytevector-truncate!
(binary-port-output-buffer p)
(binary-port-output-index p))]
[bv* (bv-list-op-info-bv* info)])
(let ([size (fx+ (bytevector-length bv) (fx* (length bv*) chunk-size))])
(set-binary-port-output-buffer! p #vu8())
(bv-list-op-info-bv*-set! info '())
(values (reverse (if (eqv? bv #vu8()) bv* (cons bv bv*))) size))))))
(set-who! $open-bytevector-list-output-port
(lambda ()
(let ([p ($make-binary-output-port "bytevector-list"
$bytevector-list-output-handler
#vu8()
(make-bv-list-op-info #f '()))])
($set-port-flags! p (constant port-flag-block-buffered))
(values p (lambda () (extractor p)))))))
(let ()
(define ($call-with-bytevector-output-port who proc maybe-transcoder)
(let-values ([(port extractor) (open-bytevector-output-port maybe-transcoder)])
(proc port)
(let ([bv (extractor)])
(call-port-handler close-port who port)
bv)))
(set-who! call-with-bytevector-output-port
(case-lambda
[(proc)
(unless (procedure? proc) ($oops who "~s is not a procedure" proc))
($call-with-bytevector-output-port who proc #f)]
[(proc maybe-transcoder)
(unless (procedure? proc)
($oops who "~s is not a procedure" proc))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not a transcoder" maybe-transcoder))
($call-with-bytevector-output-port who proc maybe-transcoder)]))
(set-who! string->bytevector
(lambda (str tx)
(unless (string? str)
($oops who "~s is not a string" str))
(unless ($transcoder? tx)
($oops who "~s is not a transcoder" tx))
($call-with-bytevector-output-port who
(lambda (op) (put-string op str))
tx))))
;; open-string-output-port
(let ()
;; see open-bytevector-output-port for explanation of algorithm
(define-record-type string-output-port-info
(nongenerative)
(opaque #t)
(sealed #t)
(fields
(mutable index)
(mutable length)
(mutable nonblocking)))
;; NOTE: leaves index at 0, callers must reset index if needed
(define (extend-buffer p count)
(let ([old-size (textual-port-output-size p)]
[old-buffer (textual-port-output-buffer p)]
[old-index (textual-port-output-index p)])
(let* ([new-length (fxmax string-buffer-length
(fx* 2 (fx+ old-size count)))]
[new-buffer (make-string new-length)])
(string-copy! old-buffer 0 new-buffer 0
(fxmin (string-length old-buffer) old-size))
(set-textual-port-output-buffer! p new-buffer))))
(define port-length
(lambda (who p)
(let ([info ($port-info p)]
[index (textual-port-output-index p)])
(let ([info-index (string-output-port-info-index info)]
[info-length (string-output-port-info-length info)])
(if (eq? index info-index)
info-length ;; last op was set-pos
(max index info-length)))))) ;; last op was put
(define $string-output-handler
(make-port-handler
[ready? #f]
[lookahead #f]
[unget #f]
[get #f]
[get-some #f]
[clear-input #f]
[put
(lambda (who p x)
(assert-not-closed who p)
(let ([index (textual-port-output-index p)])
(when (port-output-full? p) (extend-buffer p 0))
(string-set! (textual-port-output-buffer p) index x)
(set-textual-port-output-index! p (fx1+ index))))]
[put-some
(lambda (who p st start count)
(assert-not-closed who p)
(let ([index (textual-port-output-index p)])
(when ($fxu< (textual-port-output-count p) count) (extend-buffer p count))
(string-copy! st start
(textual-port-output-buffer p) index count)
(set-textual-port-output-index! p (fx+ index count)))
count)]
[flush ; no-op on string output ports
(lambda (who p)
(assert-not-closed who p))]
[clear-output ; no-op on string output ports
(lambda (who p)
(assert-not-closed who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
; sync info-index for possible post-close extraction
(let ([info ($port-info p)] [index (textual-port-output-index p)])
(unless (eq? index (string-output-port-info-index info))
(string-output-port-info-length-set! info
(fxmax index (string-output-port-info-length info)))))
(mark-port-closed! p)
(set-textual-port-output-size! p 0)))]
[port-position
(lambda (who p)
(assert-not-closed who p)
(textual-port-output-index p))]
[set-port-position!
(lambda (who p pos)
(assert-not-closed who p)
(unless (and (fixnum? pos) (fx>= pos 0))
(if (and (bignum? pos) (>= pos 0))
(position-oops who p pos "out of range")
($oops who "~s is not a valid position" pos)))
(let ([info ($port-info p)]
[index (textual-port-output-index p)])
; unless last op was set-pos, save the true length
(unless (eq? index (string-output-port-info-index info))
(string-output-port-info-length-set! info
(fxmax index (string-output-port-info-length info))))
(set-textual-port-output-size! p
(fxmax pos (fx1- (string-length (textual-port-output-buffer p)))))
(set-textual-port-output-index! p pos)
(string-output-port-info-index-set! info pos)))]
[port-length
(lambda (who p)
(assert-not-closed who p)
(port-length who p))]
[set-port-length!
(lambda (who p pos)
(unless (and (fixnum? pos) (fx>= pos 0))
(if (and (bignum? pos) (>= pos 0))
(position-oops who p pos "out of range")
($oops who "~s is not a valid length" pos)))
(assert-not-closed who p)
(let ([info ($port-info p)]
[index (textual-port-output-index p)]
[size (textual-port-output-size p)])
;; ensure the bytevector is long enough
(let ([buflen-1 (fx1- (string-length (textual-port-output-buffer p)))])
(when ($fxu< buflen-1 pos)
(extend-buffer p (fx- pos buflen-1))
(set-textual-port-output-index! p index)))
;; make it look like a set-pos was done last
;; (i.e. index might be beyond true length)
(string-output-port-info-index-set! info index)
;; set the true length
(string-output-port-info-length-set! info pos)))]
[port-nonblocking?
(lambda (who p)
(assert-not-closed who p)
(string-output-port-info-nonblocking ($port-info p)))]
[set-port-nonblocking!
(lambda (who p x)
(assert-not-closed who p)
(string-output-port-info-nonblocking-set! ($port-info p) x))]))
(define ($open-string-output-port)
(let ([p ($make-textual-output-port "string"
$string-output-handler
""
(make-string-output-port-info 0 0 #f))])
($set-port-flags! p (constant port-flag-block-buffered))
($set-port-flags! p (constant port-flag-char-positions))
($set-port-flags! p (constant port-flag-bol))
p))
(define ($get-output-string p)
(let ([old-buffer
(string-truncate!
(textual-port-output-buffer p)
(port-length #f p))])
(set-textual-port-output-buffer! p "")
(let ([info ($port-info p)])
(string-output-port-info-index-set! info 0)
(string-output-port-info-length-set! info 0))
old-buffer))
(set-who! open-string-output-port
(lambda ()
(let ([p ($open-string-output-port)])
(values p (lambda () ($get-output-string p))))))
(set-who! open-output-string
(lambda ()
($open-string-output-port)))
(set-who! get-output-string
(lambda (p)
(unless (and (port? p) (eq? ($port-handler p) $string-output-handler))
($oops who "~s is not a string output port" p))
($get-output-string p)))
)
(set-who! call-with-string-output-port
(lambda (proc)
(unless (procedure? proc)
($oops who "~s is not a procedure" proc))
(let-values ([(port extractor) (open-string-output-port)])
(proc port)
(let ([st (extractor)])
(call-port-handler close-port who port)
st))))
;; current-output-port and current-error-port are in prims.ss
(set-who! make-custom-binary-output-port
(lambda (id write! get-position set-position! close)
(unless (string? id) ($oops who "~s is not a string" id))
(unless (procedure? write!) ($oops who "~s is not a procedure" write!))
(unless (or (not get-position) (procedure? get-position))
($oops who "~s is not a procedure or #f" get-position))
(unless (or (not set-position!) (procedure? set-position!))
($oops who "~s is not a procedure or #f" set-position!))
(unless (or (not close) (procedure? close))
($oops who "~s is not a procedure or #f" close))
(let ([handler
(make-port-handler
[ready? #f]
[lookahead #f]
[unget #f]
[get #f]
[get-some #f]
[clear-input #f]
[put
(lambda (who p x)
(assert-not-closed who p)
(binary-custom-port-put who p write! x))]
[put-some
(lambda (who p bv start count)
(assert-not-closed who p)
(binary-custom-port-put-some who p write! bv start count))]
[flush
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-flush who p write!))]
[clear-output
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-clear-output who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
(binary-custom-port-flush who p write!)
(binary-custom-port-close-port who p close)))]
[port-position
(and get-position
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-port-position out who p get-position)))]
[set-port-position!
(and set-position!
(lambda (who p x)
(unless (or (and (fixnum? x) (fx>= x 0)) (and (bignum? x) (>= x 0)))
($oops who "~s is not a valid position" x))
(assert-not-closed who p)
(binary-custom-port-flush who p write!)
(set-position! x)))]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f])])
(let ([bufsiz (custom-port-buffer-size)])
(let ([p ($make-binary-output-port id handler (make-bytevector bufsiz) #f)])
($set-port-flags! p (constant port-flag-block-buffered))
(set-binary-port-output-size! p (fx1- bufsiz)) ;; leave room for put to work
p)))))
(set-who! make-custom-textual-output-port
(lambda (id write! get-position set-position! close)
(unless (string? id) ($oops who "~s is not a string" id))
(unless (procedure? write!) ($oops who "~s is not a procedure" write!))
(unless (or (not get-position) (procedure? get-position))
($oops who "~s is not a procedure or #f" get-position))
(unless (or (not set-position!) (procedure? set-position!))
($oops who "~s is not a procedure or #f" set-position!))
(unless (or (not close) (procedure? close))
($oops who "~s is not a procedure or #f" close))
(let ([handler
(make-port-handler
[ready? #f]
[lookahead #f]
[unget #f]
[get #f]
[get-some #f]
[clear-input #f]
[put
(lambda (who p x)
(assert-not-closed who p)
(textual-custom-port-put who p write! x))]
[put-some
(lambda (who p str start count)
(assert-not-closed who p)
(textual-custom-port-put-some who p write! str start count))]
[flush
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-flush who p write!))]
[clear-output
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-clear-output who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
(textual-custom-port-flush who p write!)
(textual-custom-port-close-port who p close)))]
[port-position
(and get-position
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-flush who p write!)
(get-position)))]
[set-port-position!
(and set-position!
(lambda (who p x)
(assert-not-closed who p)
(textual-custom-port-flush who p write!)
(set-position! x)))]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f])])
(let ([bufsiz (custom-port-buffer-size)])
(let ([p ($make-textual-output-port id handler (make-string bufsiz) #f)])
($set-port-flags! p (constant port-flag-block-buffered))
($set-port-flags! p (constant port-flag-bol))
(set-textual-port-output-size! p (fx1- bufsiz)) ;; leave room for put to work
p)))))
;;;; 8.2.11 Binary output
;; put-u8 in prims.ss
(set-who! put-bytevector
(case-lambda
[(binary-output-port bv)
(unless (and (output-port? binary-output-port) (binary-port? binary-output-port))
($oops who "~s is not a binary output port" binary-output-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(#3%put-bytevector binary-output-port bv)]
[(binary-output-port bv start)
(unless (and (output-port? binary-output-port) (binary-port? binary-output-port))
($oops who "~s is not a binary output port" binary-output-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless (and (fixnum? start) (not ($fxu< (bytevector-length bv) start)))
($oops who "invalid start value ~s" start))
(#3%put-bytevector binary-output-port bv start)]
[(binary-output-port bv start count)
(unless (and (output-port? binary-output-port) (binary-port? binary-output-port))
($oops who "~s is not a binary output port" binary-output-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless (and (fixnum? start) (fx<= 0 start))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx<= 0 count))
($oops who "invalid count ~s" count))
(unless (fx<= count (fx- (bytevector-length bv) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count bv))
(#3%put-bytevector binary-output-port bv start count)]))
;; not in R6RS
(set-who! put-bytevector-some
(case-lambda
[(binary-output-port bv)
(unless (and (output-port? binary-output-port) (binary-port? binary-output-port))
($oops who "~s is not a binary output port" binary-output-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(#3%put-bytevector-some binary-output-port bv)]
[(binary-output-port bv start)
(unless (and (output-port? binary-output-port) (binary-port? binary-output-port))
($oops who "~s is not a binary output port" binary-output-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless (and (fixnum? start) (not ($fxu< (bytevector-length bv) start)))
($oops who "invalid start value ~s" start))
(#3%put-bytevector-some binary-output-port bv start)]
[(binary-output-port bv start count)
(unless (and (output-port? binary-output-port) (binary-port? binary-output-port))
($oops who "~s is not a binary output port" binary-output-port))
(unless (bytevector? bv)
($oops who "~s is not a bytevector" bv))
(unless (and (fixnum? start) (fx<= 0 start))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx<= 0 count))
($oops who "invalid count ~s" count))
(unless (fx<= count (fx- (bytevector-length bv) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count bv))
(#3%put-bytevector-some binary-output-port bv start count)]))
;;;; 8.2.12 Textual output
;; put-char in prims.ss
(set-who! put-string
(case-lambda
[(textual-output-port str)
(unless (and (output-port? textual-output-port) (textual-port? textual-output-port))
($oops who "~s is not a textual output port" textual-output-port))
(unless (string? str)
($oops who "~s is not a string" str))
(#3%put-string textual-output-port str)]
[(textual-output-port str start)
(unless (and (output-port? textual-output-port) (textual-port? textual-output-port))
($oops who "~s is not a textual output port" textual-output-port))
(unless (string? str)
($oops who "~s is not a string" str))
(unless (and (fixnum? start) (not ($fxu< (string-length str) start)))
($oops who "invalid start value ~s" start))
(#3%put-string textual-output-port str start)]
[(textual-output-port str start count)
(unless (and (output-port? textual-output-port) (textual-port? textual-output-port))
($oops who "~s is not a textual output port" textual-output-port))
(unless (string? str)
($oops who "~s is not a string" str))
(unless (and (fixnum? start) (fx<= 0 start))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx<= 0 count))
($oops who "invalid count value ~s" count))
(unless (fx<= count (fx- (string-length str) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count str))
(#3%put-string textual-output-port str start count)]))
;; not in R6RS
(set-who! put-string-some
(case-lambda
[(textual-output-port str)
(unless (and (output-port? textual-output-port) (textual-port? textual-output-port))
($oops who "~s is not a textual output port" textual-output-port))
(unless (string? str)
($oops who "~s is not a string" str))
(#3%put-string-some textual-output-port str)]
[(textual-output-port str start)
(unless (and (output-port? textual-output-port) (textual-port? textual-output-port))
($oops who "~s is not a textual output port" textual-output-port))
(unless (string? str)
($oops who "~s is not a string" str))
(unless (and (fixnum? start) (not ($fxu< (string-length str) start)))
($oops who "invalid start value ~s" start))
(#3%put-string-some textual-output-port str start)]
[(textual-output-port str start count)
(unless (and (output-port? textual-output-port) (textual-port? textual-output-port))
($oops who "~s is not a textual output port" textual-output-port))
(unless (string? str)
($oops who "~s is not a string" str))
(unless (and (fixnum? start) (fx<= 0 start))
($oops who "invalid start value ~s" start))
(unless (and (fixnum? count) (fx<= 0 count))
($oops who "invalid count value ~s" count))
(unless (fx<= count (fx- (string-length str) start)) ; avoid overflow
($oops who "index ~s + count ~s is beyond the end of ~s" start count str))
(#3%put-string-some textual-output-port str start count)]))
;; put-datum in print.ss
;;;; 8.2.13 Input/output ports
;; open-file-input/output-port
(let ()
(define open-binary-file-input/output-port
(lambda (who filename options perms b-mode)
(let ([no-create (enum-set-subset? (file-options no-create) options)]
[no-fail (enum-set-subset? (file-options no-fail) options)]
[no-truncate (enum-set-subset? (file-options no-truncate) options)]
[append (enum-set-subset? (file-options append) options)]
[lock (enum-set-subset? (file-options exclusive) options)]
[replace (enum-set-subset? (file-options replace) options)]
[compressed (enum-set-subset? (file-options compressed) options)])
(when (and compressed lock)
($oops who "exclusive option is not supported with compress option"))
(when-feature windows
(unless-feature pthreads
; try to work around windows file open semantics by trying
; to close any open ports to the file if we cannot delete it
; without doing so.
(when replace
(delete-file filename #f)
(when (file-exists? filename)
(collect (collect-maximum-generation))))))
(let ([fd (critical-section
($open-input/output-fd filename perms
no-create no-fail no-truncate
append lock replace compressed))])
(when (pair? fd) (open-oops who filename options fd))
(open-binary-fd-input/output-port who filename fd #t b-mode lock compressed)))))
(define help-open-file-input/output-port
(lambda (who filename options perms b-mode maybe-transcoder)
(let ([bp (open-binary-file-input/output-port who filename options perms b-mode)])
(if maybe-transcoder
(transcoded-port bp maybe-transcoder)
bp))))
(set-who! open-file-input/output-port
(rec open-file-input/output-port
(case-lambda
[(filename) (open-file-input/output-port filename (file-options))]
[(filename options) (open-file-input/output-port filename options (buffer-mode block))]
[(filename options b-mode) (open-file-input/output-port filename options b-mode #f)]
[(filename options b-mode maybe-transcoder)
(unless (string? filename) ($oops who "~s is not a string" filename))
(unless (and (enum-set? options) (enum-set-subset? options $file-options))
($oops who "~s is not a file-options object" options))
(unless (buffer-mode? b-mode) ($oops who "~s is not a valid buffer mode" b-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(help-open-file-input/output-port who filename options
(extract-permission-mask options) b-mode maybe-transcoder)])))
(set! $open-file-input/output-port
(rec $open-file-input/output-port
(case-lambda
[(who filename) ($open-file-input/output-port who filename (file-options))]
[(who filename options) ($open-file-input/output-port who filename options (buffer-mode block))]
[(who filename options b-mode) ($open-file-input/output-port who filename options b-mode #f)]
[(who filename options b-mode maybe-transcoder)
(unless (string? filename) ($oops who "~s is not a string" filename))
(unless (and (enum-set? options) (enum-set-subset? options $file-options))
($oops who "~s is not a file-options object" options))
(unless (buffer-mode? b-mode) ($oops who "~s is not a valid buffer mode" b-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(help-open-file-input/output-port who filename options
(extract-permission-mask options) b-mode maybe-transcoder)])))
(set-who! open-fd-input/output-port
(case-lambda
[(fd)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(open-binary-fd-input/output-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) (buffer-mode block) #f #f)]
[(fd buffer-mode)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(unless (buffer-mode? buffer-mode)
($oops who "~s is not a buffer mode" buffer-mode))
(open-binary-fd-input/output-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) buffer-mode #f #f)]
[(fd buffer-mode maybe-transcoder)
(unless (and (fixnum? fd) (fx>= fd 0))
($oops who "~s is not a file descriptor" fd))
(unless (buffer-mode? buffer-mode)
($oops who "~s is not a buffer mode" buffer-mode))
(unless (or (not maybe-transcoder) ($transcoder? maybe-transcoder))
($oops who "~s is not #f or a transcoder" maybe-transcoder))
(let ([binary-port
(open-binary-fd-input/output-port who (format "fd ~s" fd) (make-fd fd) ($fd-regular? fd) buffer-mode #f #f)])
(if maybe-transcoder
(transcoded-port binary-port maybe-transcoder)
binary-port))]))
; TODO: standard-input/output-port. requires paired fds
; simple i/o routines here to share helpers
(let ()
(define (oiof who s o)
(unless (string? s) ($oops who "~s is not a string" s))
(let ([o (if (list? o) o (list o))])
(let loop ([o o] [ifexists #f] [mode #o666] [xmode #f] [bmode #f])
(if (null? o)
(help-open-file-input/output-port who s
(enum-set-union
(case ifexists
[(error) (file-options)]
[(truncate) (file-options no-fail)]
[(replace) (file-options no-fail no-truncate replace)]
[(append) (file-options append no-fail no-truncate)]
[else (file-options no-fail no-truncate)])
(if (eq? xmode 'exclusive) (file-options exclusive) (file-options)))
mode
(if (eq? bmode 'unbuffered) (buffer-mode none) (buffer-mode block))
(current-transcoder))
(case (car o)
[(error truncate replace append)
(check-option who ifexists (car o))
(loop (cdr o) (car o) mode xmode bmode)]
[(buffered unbuffered)
(check-option who bmode (car o))
(loop (cdr o) ifexists mode xmode (car o))]
[(exclusive nonexclusive)
(check-option who xmode (car o))
(loop (cdr o) ifexists mode (car o) bmode)]
[(mode)
(if (null? (cdr o))
($oops who "mode option requires an argument")
(let ([mode (cadr o)])
(if (and (fixnum? mode) (fx>= mode 0))
(loop (cddr o) ifexists mode xmode bmode)
($oops who "mode argument must be a nonnegative fixnum"))))]
[else ($oops who "invalid option ~s" (car o))])))))
(set-who! open-input-output-file
(case-lambda
[(s) (oiof who s '())]
[(s o) (oiof who s o)])))
)
;; make-custom-binary-input/output-port
(let ()
(define-syntax make-ready-for-input
(syntax-rules ()
[(_ who p_ write!)
(let ([p p_])
(unless (eq? 0 (binary-port-output-size p))
(binary-custom-port-flush who p write!)
;; don't set input-size; it is set only after a read
(set-binary-port-output-size! p 0)))]))
(module ((make-ready-for-output $make-ready-for-output))
(define $make-ready-for-output
(lambda (who p get-position set-position!)
(unless (eq? (binary-port-input-size p) 0)
(unless (port-input-empty? p)
(if (not (and get-position set-position!))
(position-warning who
(if get-position
"cannot set position for write after read on ~s"
"cannot determine position for write after read on ~s")
p)
(set-position! (- (get-position) (binary-port-input-count p)))))
(set-binary-port-input-size! p 0))
(set-port-eof! p #f)
(set-binary-port-output-size! p
(fx1- (bytevector-length (binary-port-output-buffer p))))))
(define-syntax make-ready-for-output
(syntax-rules ()
[(_ ?who ?p ?get-position ?set-position!)
(let ([p ?p])
(when (eq? (binary-port-output-size p) 0)
($make-ready-for-output ?who p ?get-position ?set-position!)))])))
;; Ports start with a non-ill-defined position.
;; Unless get-position and set-position! are provided,
;; doing a buffered read operation makes the position ill-defined.
;;
;; A put, put-some or (textual)port-position operation may give
;; unexpected results when the position is ill-defined.
;;
;; A set-port-position is sufficient to make
;; the position no longer ill-defined.
;;
;; Buffered read operations include lookahead, port-eof?, and unget.
;; Buffered read operations also include get and get-some if buffer-mode is not none.
(set-who! make-custom-binary-input/output-port
(lambda (id read! write! get-position set-position! close)
(unless (string? id) ($oops who "~s is not a string" id))
(unless (procedure? read!) ($oops who "~s is not a procedure" read!))
(unless (procedure? write!) ($oops who "~s is not a procedure" write!))
(unless (or (not get-position) (procedure? get-position))
($oops who "~s is not a procedure or #f" get-position))
(unless (or (not set-position!) (procedure? set-position!))
($oops who "~s is not a procedure or #f" set-position!))
(unless (or (not close) (procedure? close))
($oops who "~s is not a procedure or #f" close))
(let ([handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(or (not (port-input-empty? p))
(port-flag-eof-set? p)
(read-oops who p "cannot determine ready status")))]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(binary-custom-port-lookahead who p read!))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(binary-custom-port-unget who p x))]
[get
(lambda (who p)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(binary-custom-port-get who p read!))]
[get-some
(lambda (who p bv start count)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(binary-custom-port-get-some who p read! bv start count))]
[clear-input
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-clear-input who p))]
[put
(lambda (who p x)
(assert-not-closed who p)
(make-ready-for-output who p get-position set-position!)
(binary-custom-port-put who p write! x))]
[put-some
(lambda (who p bv start count)
(assert-not-closed who p)
(make-ready-for-output who p get-position set-position!)
(binary-custom-port-put-some who p write! bv start count))]
[flush
(lambda (who p)
(assert-not-closed who p)
; binary-custom-port-flush must be a no-op in input mode
(binary-custom-port-flush who p write!))]
[clear-output
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-clear-output who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
; binary-custom-port-flush must be a no-op in input mode
(binary-custom-port-flush who p write!)
(binary-custom-port-close-port who p close)))]
[port-position
(and get-position
(lambda (who p)
(assert-not-closed who p)
(binary-custom-port-port-position in/out who p get-position)))]
[set-port-position!
(and set-position!
(lambda (who p x)
(unless (or (and (fixnum? x) (fx>= x 0)) (and (bignum? x) (>= x 0)))
($oops who "~s is not a valid position" x))
(assert-not-closed who p)
(binary-custom-port-flush who p write!)
(set-binary-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f)
(set-position! x)))]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f])])
(let ([bufsiz (custom-port-buffer-size)])
(let ([p ($make-binary-input/output-port id handler
(make-bytevector bufsiz)
(make-bytevector bufsiz)
#f)])
($set-port-flags! p (constant port-flag-block-buffered))
(set-binary-port-input-size! p 0)
(set-binary-port-output-size! p (fx1- bufsiz)) ;; leave room for put to work
p))))))
;; make-custom-textual-input/output-port
(let ()
(define-syntax make-ready-for-input
(syntax-rules ()
[(_ who p_ write!)
(let ([p p_])
(unless (eq? 0 (textual-port-output-size p))
(textual-custom-port-flush who p write!)
;; don't set input-size; it is set only after a read
(set-textual-port-output-size! p 0)))]))
(module ((make-ready-for-output $make-ready-for-output))
(define $make-ready-for-output
(lambda (who p get-position set-position!)
(unless (eq? (textual-port-input-size p) 0)
(unless (port-input-empty? p)
(position-warning who "cannot set position for write after read on ~s" p))
(set-textual-port-input-size! p 0))
(set-port-eof! p #f)
(set-textual-port-output-size! p
(fx1- (string-length (textual-port-output-buffer p))))))
(define-syntax make-ready-for-output
(syntax-rules ()
[(_ ?who ?p ?get-position ?set-position!)
(let ([p ?p])
(when (eq? (textual-port-output-size p) 0)
($make-ready-for-output ?who p ?get-position ?set-position!)))])))
;; Ports start with a non-ill-defined position.
;; Unless get-position and set-position! are provided,
;; doing a buffered read operation makes the position ill-defined.
;;
;; A put, put-some or (textual)port-position operation may give
;; unexpected results when the position is ill-defined.
;;
;; A set-port-position is sufficient to make
;; the position no longer ill-defined.
;;
;; Buffered read operations include lookahead, port-eof?, and unget.
;; Buffered read operations also include get and get-some if buffer-mode is not none.
(set-who! make-custom-textual-input/output-port
(lambda (id read! write! get-position set-position! close)
(unless (string? id) ($oops who "~s is not a string" id))
(unless (procedure? read!) ($oops who "~s is not a procedure" read!))
(unless (procedure? write!) ($oops who "~s is not a procedure" write!))
(unless (or (not get-position) (procedure? get-position))
($oops who "~s is not a procedure or #f" get-position))
(unless (or (not set-position!) (procedure? set-position!))
($oops who "~s is not a procedure or #f" set-position!))
(unless (or (not close) (procedure? close))
($oops who "~s is not a procedure or #f" close))
(let ([handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(or (not (port-input-empty? p))
(port-flag-eof-set? p)
(read-oops who p "cannot determine ready status")))]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(textual-custom-port-lookahead who p write!))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(textual-custom-port-unget who p x))]
[get
(lambda (who p)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(textual-custom-port-get who p read!))]
[get-some
(lambda (who p str start count)
(assert-not-closed who p)
(make-ready-for-input who p write!)
(textual-custom-port-get-some who p read! str start count))]
[clear-input
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-clear-input who p))]
[put
(lambda (who p x)
(assert-not-closed who p)
(make-ready-for-output who p get-position set-position!)
(textual-custom-port-put who p write! x))]
[put-some
(lambda (who p str start count)
(assert-not-closed who p)
(make-ready-for-output who p get-position set-position!)
(textual-custom-port-put-some who p write! str start count))]
[flush
(lambda (who p)
(assert-not-closed who p)
; textual-custom-port-flush must be a no-op in input mode
(textual-custom-port-flush who p write!))]
[clear-output
(lambda (who p)
(assert-not-closed who p)
(textual-custom-port-clear-output who p))]
[close-port
(lambda (who p)
(unless (port-closed? p)
; textual-custom-port-flush must be a no-op in input mode
(textual-custom-port-flush who p write!)
(textual-custom-port-close-port who p close)))]
[port-position
(and get-position
(lambda (who p)
(assert-not-closed who p)
(unless (port-input-empty? p)
(position-warning who
"cannot determine accurate position after read on ~s"
p))
(textual-custom-port-flush who p write!)
(get-position)))]
[set-port-position!
(and set-position!
(lambda (who p x)
(assert-not-closed who p)
(textual-custom-port-flush who p write!)
(set-textual-port-input-size! p 0) ;; junk the buffer data
(set-port-eof! p #f)
(set-position! x)))]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f])])
(let ([bufsiz (custom-port-buffer-size)])
(let ([p ($make-textual-input/output-port id handler
(make-string bufsiz)
(make-string bufsiz)
#f)])
($set-port-flags! p (constant port-flag-block-buffered))
($set-port-flags! p (constant port-flag-bol))
(set-textual-port-input-size! p 0)
(set-textual-port-output-size! p (fx1- bufsiz)) ;; leave room for put to work
p))))))
;;;; 8.3 Simple I/O: (rnrs io simple (6))
(let ()
;; eof-object in 8.2
;; eof-object? in 8.2
;; call-with-input-file in 8.2 (to share helpers)
;; call-with-output-file in 8.2 (to share helpers)
;; input-port? in 8.2
;; output-port? in 8.2
;; current-input-port in 8.2
;; current-output-port in 8.2
;; current-error-port in 8.2
;; with-input-from-file in 8.2 (to share helpers)
;; with-output-to-file in 8.2 (to share helpers)
;; open-input-file in 8.2 (to share helpers)
;; open-output-file in 8.2 (to share helpers)
(set-who! close-input-port
(lambda (input-port)
(unless (input-port? input-port)
($oops who "~s is not an input port" input-port))
(close-port input-port)))
(set-who! close-output-port
(lambda (output-port)
(unless (output-port? output-port)
($oops who "~s is not an output port" output-port))
(close-port output-port)))
)
(let ()
(define ($block-read who p s count)
(if (fx= count 0)
(if (port-eof? p) (eof-object) 0)
(call-port-handler get-some who p s 0 count)))
(set-who! block-read
(case-lambda
[(p s)
(unless (and (input-port? p) (textual-port? p))
($oops who "~s is not a textual input port" p))
(unless (string? s)
($oops who "invalid buffer argument ~s" s))
($block-read who p s (string-length s))]
[(p s n)
(unless (and (input-port? p) (textual-port? p))
($oops who "~s is not a textual input port" p))
(unless (string? s) ($oops who "invalid buffer argument ~s" s))
(unless (and (fixnum? n) (fx<= 0 n (string-length s)))
($oops who "invalid count argument ~s" n))
($block-read who p s n)])))
(let ()
(define ($block-write who p s count)
(let loop ([i 0] [count count])
(unless (fx= count 0)
(let ([n (call-port-handler put-some who p s i count)])
(loop (fx+ i n) (fx- count n)))))
(call-port-handler flush who p))
(set-who! block-write
(case-lambda
[(p s)
(unless (and (output-port? p) (textual-port? p))
($oops who "~s is not a textual output port" p))
(unless (string? s) ($oops who "invalid buffer argument ~s" s))
($block-write who p s (string-length s))]
[(p s n)
(unless (and (output-port? p) (textual-port? p))
($oops who "~s is not a textual output port" p))
(unless (string? s) ($oops who "invalid buffer argument ~s" s))
(unless (and (fixnum? n) (fx<= 0 n (string-length s)))
($oops who "invalid count argument ~s" n))
($block-write who p s n)])))
(let ()
(define ($char-ready? input-port who)
(or (not (port-input-empty? input-port))
(port-flag-eof-set? input-port)
(call-port-handler ready? who input-port)))
(set-who! char-ready?
(case-lambda
[() ($char-ready? (current-input-port) who)]
[(input-port)
(unless (and (input-port? input-port) (textual-port? input-port))
($oops who "~s is not a textual input port" input-port))
($char-ready? input-port who)])))
(set-who! clear-input-port
(rec clear-input-port
(case-lambda
[() (let ([p (current-input-port)])
(call-port-handler clear-input who p))]
[(p)
(unless (input-port? p)
($oops who "~s is not an input port" p))
(call-port-handler clear-input who p)])))
(set-who! clear-output-port
(rec clear-output-port
(case-lambda
[() (let ([p (current-output-port)])
(call-port-handler clear-output who p))]
[(p)
(unless (output-port? p)
($oops who "~s is not an output port" p))
(call-port-handler clear-output who p)])))
(set-who! fresh-line
(rec fresh-line
(case-lambda
[() (fresh-line (current-output-port))]
[(p)
(unless (and (output-port? p) (textual-port? p))
($oops who "~s is not a textual output port" p))
(assert-not-closed who p)
(unless ($textual-port-bol? p)
(call-port-handler put who p #\newline))])))
(set-who! port-bol?
(lambda (p)
(unless (and (output-port? p) (textual-port? p))
($oops who "~s is not a textual output port" p))
(assert-not-closed who p)
($textual-port-bol? p)))
(let ()
(define (binary-fd-port? bp)
($port-flags-set? bp (constant port-flag-file)))
(set-who! file-port?
(lambda (p)
(unless (port? p) ($oops who "~s is not a port" p))
(if (binary-port? p)
(binary-fd-port? p)
(let ([info ($port-info p)])
(and (codec-info? info) (binary-fd-port? (codec-info-bp info)))))))
(set-who! port-file-descriptor
(let ()
(define gzfile-fd (foreign-procedure "(cs)gzxfile_fd" (ptr) int))
(define (binary-port-fd p bp)
(unless (binary-fd-port? bp)
($oops who "~s is not a file port" p))
(let ([x ($port-info bp)])
(if (port-gz-mode bp)
(gzfile-fd x)
x)))
(lambda (p)
(unless (port? p) ($oops who "~s is not a port" p))
(if (binary-port? p)
(binary-port-fd p p)
(let ([info ($port-info p)])
(unless (codec-info? info)
($oops who "~s is not a file port" p))
(binary-port-fd p (codec-info-bp info))))))))
(let ()
(define $generic-port-handler
(make-port-handler
[ready?
(lambda (who p)
(assert-not-closed who p)
(and (($port-info p) 'char-ready? p) #t))]
[lookahead
(lambda (who p)
(assert-not-closed who p)
(let ([c (($port-info p) 'peek-char p)])
(unless (or (char? c) (eof-object? c))
($oops 'generic-port-handler "invalid peek-char return value ~s" c))
c))]
[unget
(lambda (who p x)
(assert-not-closed who p)
(unless (eof-object? x) (($port-info p) 'unread-char x p))
(void))]
[get
(lambda (who p)
(assert-not-closed who p)
(let ([c (($port-info p) 'read-char p)])
(unless (or (char? c) (eof-object? c))
($oops 'generic-port-handler "invalid read-char return value ~s" c))
c))]
[get-some
(lambda (who p st start count)
(if (= start 0)
(let ([n (($port-info p) 'block-read p st count)])
(unless (or (and (fixnum? n) (not ($fxu< count n)))
(eof-object? n))
($oops 'generic-port-handler "invalid block-read return value ~s on ~s" n p))
n)
(let ([tmp (make-string count)])
(let ([n (($port-info p) 'block-read p tmp count)])
(cond
[(and (fixnum? n) (not ($fxu< count n)))
(string-copy! tmp 0 st start n)
n]
[(eof-object? n) n]
[else ($oops 'generic-port-handler "invalid block-read return value ~s on ~s" n p)])))))]
[clear-input
(lambda (who p)
(assert-not-closed who p)
(($port-info p) 'clear-input-port p)
(void))]
[put
(lambda (who p x)
(assert-not-closed who p)
(($port-info p) 'write-char x p)
(void))]
[put-some
(lambda (who p st start count)
(assert-not-closed who p)
(if (= start 0)
(($port-info p) 'block-write p st count)
(let ([tmp (make-string count)])
(string-copy! st start tmp 0 count)
(($port-info p) 'block-write p tmp count)))
count)]
[flush
(lambda (who p)
(assert-not-closed who p)
(($port-info p) 'flush-output-port p)
(void))]
[clear-output
(lambda (who p)
(assert-not-closed who p)
(($port-info p) 'clear-output-port p)
(void))]
[close-port
(lambda (who p)
(unless (port-closed? p)
(($port-info p) 'close-port p))
(void))]
[port-position
(lambda (who p)
(assert-not-closed who p)
(($port-info p) 'file-position p))]
[set-port-position!
(lambda (who p x)
(assert-not-closed who p)
(($port-info p) 'file-position p x))]
[port-length
(lambda (who p)
(assert-not-closed who p)
(($port-info p) 'file-length p))]
[set-port-length!
(lambda (who p pos)
(assert-not-closed who p)
(($port-info p) 'truncate-file p pos))]
[port-nonblocking? #f]
[set-port-nonblocking! #f]))
(define (set-name p)
(guard (c [#t (void)])
(let ([name (($port-info p) 'port-name p)])
(when (string? name) (set-port-name! p name))))
p)
(set-who! make-input-port
(lambda (handler buffer)
(unless (procedure? handler)
(if (and (fixnum? handler) (fx>= handler 0))
($oops who "fixnum handler no longer supported; use open-fd-input-port")
($oops who "~s is not a procedure" handler)))
(unless (string? buffer) ($oops who "~s is not a string" buffer))
(set-name
($make-textual-input-port "generic"
$generic-port-handler
buffer handler))))
(set-who! make-output-port
(lambda (handler buffer)
(unless (procedure? handler)
(if (and (fixnum? handler) (fx>= handler 0))
($oops who "fixnum handler no longer supported; use open-fd-input-port")
($oops who "~s is not a procedure" handler)))
(unless (string? buffer) ($oops who "~s is not a string" buffer))
(set-name
($make-textual-output-port "generic"
$generic-port-handler
buffer handler))))
(set-who! make-input/output-port
(lambda (handler ibuffer obuffer)
(unless (procedure? handler)
(if (and (fixnum? handler) (fx>= handler 0))
($oops who "fixnum handler no longer supported; use open-fd-input-port")
($oops who "~s is not a procedure" handler)))
(unless (string? ibuffer) ($oops who "~s is not a string" ibuffer))
(unless (string? obuffer) ($oops who "~s is not a string" obuffer))
(set-name
($make-textual-input/output-port "generic"
$generic-port-handler
ibuffer obuffer handler))))
(set-who! port-handler
(let ()
(define check
(lambda (msg n)
(unless (cond
[(assq n
'((1 char-ready? clear-input-port clear-output-port close-port
file-length file-position flush-output-port peek-char
port-name read-char)
(2 file-position unread-char write-char)
(3 block-read block-write))) =>
(lambda (ls) (memq msg (cdr ls)))]
[else #f])
($oops 'non-generic-port-handler
"cannot handle message ~s with argument count ~s"
msg n))))
(define non-generic-port-handler
(lambda (msg . args)
(check msg (length args))
(apply ($top-level-value msg) args)))
(lambda (p)
(unless (port? p) ($oops who "~s is not a port" p))
(if (eq? ($port-handler p) $generic-port-handler)
($port-info p)
non-generic-port-handler))))
)
(record-writer (type-descriptor codec)
(lambda (x p wr)
(fprintf p "#<codec ~a>" (codec-name x))))
(record-writer (type-descriptor transcoder)
(lambda (x p wr)
(fprintf p "#<transcoder ~a ~s ~s>"
(codec-name ($transcoder-codec x))
($transcoder-eol-style x)
($transcoder-error-handling-mode x))))
(set-who! #(r6rs: current-input-port)
(lambda ()
(#2%current-input-port)))
(set-who! #(r6rs: current-output-port)
(lambda ()
(#2%current-output-port)))
(set-who! #(r6rs: current-error-port)
(lambda ()
(#2%current-error-port)))
; thread-safe transcript-on, transcript-off, transcript-cafe
(let ()
(define-record-type xscript-info
(nongenerative)
(opaque #t)
(sealed #t)
(fields ip op xp (mutable ungot))
(protocol
(lambda (new)
(lambda (ip op xp)
(new ip op xp '())))))
(module (make-xscript-port xscript-port? constituent-ports)
(define-syntax with-xscript-info
(syntax-rules ()
[(_ (p ip op xp ungot) e1 e2 ...)
(andmap identifier? #'(ip op xp ungot))
(let ([x ($port-info p)])
(let ([ip (xscript-info-ip x)]
[op (xscript-info-op x)]
[xp (xscript-info-xp x)])
(define-syntax ungot
(identifier-syntax
[id (xscript-info-ungot x)]
[(set! id e) (xscript-info-ungot-set! x e)]))
e1 e2 ...))]))
(define-syntax thread-safe
(syntax-rules ()
[(_ (p ip op xp ungot) e1 e2 ...)
(with-xscript-info (p ip op xp ungot)
(with-tc-mutex e1 e2 ...))]))
(define-syntax call-xp-handler
(syntax-rules ()
[(_ msg who xp arg ...)
(identifier? #'xp)
(and (not (port-closed? xp))
(call-port-handler msg who xp arg ...))]))
(define slurp-input
(lambda (who p)
(with-xscript-info (p ip op xp ungot)
(let ([tognu (reverse ungot)])
(guard (c [#t (void)]) ; guard ready? calls
(let loop ()
(when (call-port-handler ready? who ip)
(let ([c (call-port-handler get who ip)])
(unless (eof-object? c)
(call-xp-handler put who xp c)
(set! tognu (cons c tognu))
(loop))))))
(set! ungot (reverse tognu))))))
; similar in structure to thread-safe console-port handler
(define xscript-handler
(make-port-handler
[ready?
(lambda (who p)
(thread-safe (p ip op xp ungot)
(or (not (null? ungot))
(begin
(call-port-handler flush who op)
(call-port-handler ready? who ip)))))]
[lookahead
(lambda (who p)
(thread-safe (p ip op xp ungot)
(if (not (null? ungot))
(car ungot)
(begin
(call-port-handler flush who op)
(let ([c (call-port-handler get who ip)])
(set! ungot (list c))
(unless (eof-object? c) (call-xp-handler put who xp c))
c)))))]
[unget
(lambda (who p x)
(thread-safe (p ip op xp ungot)
(set! ungot (cons x ungot))))]
[get
(lambda (who p)
(thread-safe (p ip op xp ungot)
(if (not (null? ungot))
(let ([c (car ungot)])
(set! ungot (cdr ungot))
c)
(begin
(call-port-handler flush who op)
(let ([c (call-port-handler get who ip)])
(unless (eof-object? c) (call-xp-handler put who xp c))
c)))))]
[get-some
(lambda (who p str start count)
(thread-safe (p ip op xp ungot)
(if (and (fx> count 0) (not (null? ungot)))
(let ([c (car ungot)])
(set! ungot (cdr ungot))
(if (eof-object? c)
c
(begin (string-set! str start c) 1)))
(begin
(call-port-handler flush who op)
(let ([count (call-port-handler get-some who ip str start count)])
(unless (or (eof-object? count) (fx= count 0))
(call-xp-handler put-some who xp str start count))
count)))))]
[clear-input
(lambda (who p)
(thread-safe (p ip op xp ungot)
(set! ungot '())
(call-port-handler clear-input who ip)))]
[put
(lambda (who p x)
(thread-safe (p ip op xp ungot)
(slurp-input who p)
(call-port-handler put who op x)
(call-xp-handler put who xp x)
(if ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol))
($reset-port-flags! p (constant port-flag-bol)))))]
[put-some
(lambda (who p str start count)
(thread-safe (p ip op xp ungot)
(slurp-input who p)
(let ([count (call-port-handler put-some who op str start count)])
(let f ([start start] [count count])
(unless (fx= count 0)
(let ([n (call-xp-handler put-some who xp str start count)])
(and n (f (fx+ start n) (fx- count n))))))
(if ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol))
($reset-port-flags! p (constant port-flag-bol)))
count)))]
[flush
(lambda (who p)
(thread-safe (p ip op xp ungot)
(call-port-handler flush who op)
(call-xp-handler flush who xp)))]
[clear-output
(lambda (who p)
; clearing may put op and xp out of sync, so just flush instead
(thread-safe (p ip op xp ungot)
(call-port-handler flush who op)
(call-xp-handler flush who xp)))]
[close-port
(lambda (who p)
; refuse to close transcript ports, like console ports---just flush instead
(thread-safe (p ip op xp ungot)
(call-port-handler flush who op)
(call-xp-handler flush who xp)))]
[port-position #f]
[set-port-position! #f]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f]))
(define (make-xscript-port ip op xp)
(let ([p ($make-textual-input/output-port
"transcript" xscript-handler "" ""
(make-xscript-info ip op xp))])
(when ($port-flags-set? ip (constant port-flag-r6rs))
($set-port-flags! p (constant port-flag-r6rs)))
(when ($port-flags-set? ip (constant port-flag-fold-case))
($set-port-flags! p (constant port-flag-fold-case)))
(when ($port-flags-set? ip (constant port-flag-no-fold-case))
($set-port-flags! p (constant port-flag-no-fold-case)))
(when ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol)))
p))
(define xscript-port?
(lambda (p)
(eq? ($port-handler p) xscript-handler)))
(define constituent-ports
(lambda (p)
(with-xscript-info (p ip op xp ungot)
(values ip op xp)))))
(set-who! $xscript-port? (lambda (p) (xscript-port? p)))
(set-who! $constituent-ports (lambda (p) (constituent-ports p)))
(set-who! transcript-on
(lambda (pathname)
(unless (string? pathname) ($oops who "~s is not a string" pathname))
(let ([ip (console-input-port)] [op (console-output-port)])
(when (and (guard (c [#t #f]) (char-ready? ip))
(eqv? (peek-char ip) #\newline))
(read-char ip))
(let ([xp ($open-file-output-port who pathname (file-options replace)
(buffer-mode block)
(current-transcoder))])
(let ([p (make-xscript-port ip op xp)])
(when (eq? (console-error-port) op) (console-error-port p))
(when (eq? (current-input-port) ip) (current-input-port p))
(when (eq? (current-output-port) op) (current-output-port p))
(when (eq? (current-error-port) op) (current-error-port p))
(when (eq? (trace-output-port) op) (trace-output-port p))
(console-input-port p)
(console-output-port p)))
(printf "Chez Scheme Transcript [~a]\n" (date-and-time)))))
(set-who! transcript-off
(lambda ()
(cond
[(ormap (lambda (p) (and (xscript-port? p) p))
(list (console-input-port)
(console-output-port)
(console-error-port)
(current-input-port)
(current-output-port)
(current-error-port)
(trace-output-port))) =>
(lambda (p)
(let-values ([(ip op xp) (constituent-ports p)])
(when (eq? (console-input-port) p) (console-input-port ip))
(when (eq? (console-output-port) p) (console-output-port op))
(when (eq? (console-error-port) p) (console-error-port op))
(when (eq? (current-input-port) p) (current-input-port ip))
(when (eq? (current-output-port) p) (current-output-port op))
(when (eq? (current-error-port) p) (current-error-port op))
(when (eq? (trace-output-port) p) (trace-output-port op))
(flush-output-port p)
(close-port xp)))])))
(set-who! transcript-cafe
(lambda (pathname)
(unless (string? pathname) ($oops who "~s is not a string" pathname))
(let ([ip (console-input-port)] [op (console-output-port)])
(when (and (guard (c [#t #f]) (char-ready? (console-input-port)))
(eqv? (peek-char (console-input-port)) #\newline))
(read-char (console-input-port)))
(let ([xp ($open-file-output-port who pathname (file-options replace)
(buffer-mode block)
(current-transcoder))])
(let ([p (make-xscript-port ip op xp)])
(with-values
(dynamic-wind
(lambda ()
(when (eq? (console-input-port) ip) (console-input-port p))
(when (eq? (console-output-port) op) (console-output-port p))
(when (eq? (console-error-port) op) (console-error-port p))
(when (eq? (current-input-port) ip) (current-input-port p))
(when (eq? (current-output-port) op) (current-output-port p))
(when (eq? (current-error-port) op) (current-error-port p))
(when (eq? (trace-output-port) op) (trace-output-port p)))
(lambda ()
(printf "Chez Scheme Transcript [~a]\n" (date-and-time))
(new-cafe))
(lambda ()
(when (eq? (console-input-port) p) (console-input-port ip))
(when (eq? (console-output-port) p) (console-output-port op))
(when (eq? (console-error-port) p) (console-error-port op))
(when (eq? (current-input-port) p) (current-input-port ip))
(when (eq? (current-output-port) p) (current-output-port op))
(when (eq? (current-error-port) p) (current-error-port op))
(when (eq? (trace-output-port) p) (trace-output-port op))
(flush-output-port p)))
(lambda vals
(close-port xp)
(apply values vals)))))))))
#;(let ()
(define debug-port-handler
(make-port-handler
[ready? (lambda (who p) (input-port-ready? ($port-info p)))]
[lookahead
(lambda (who p)
(let ([b (lookahead-u8 ($port-info p))])
(if (eof-object? b) b (integer->char b))))]
[unget
(lambda (who p x)
(unget-u8 ($port-info p) (if (eof-object? x) x (char->integer x))))]
[get
(lambda (who p)
(let ([b (get-u8 ($port-info p))])
(if (eof-object? b) b (integer->char b))))]
[get-some
(lambda (who p str start count)
(if (fx= count 0)
0
(let ([b (get-u8 ($port-info p))])
(if (eof-object? b)
b
(begin
(string-set! str start (integer->char b))
1)))))]
[clear-input
(lambda (who p)
(clear-input-port ($port-info p)))]
[put
(lambda (who p x)
(put-u8 ($port-info p) (char->integer x)))]
[put-some
(lambda (who p str start count)
(if (fx= count 0)
0
(begin
(put-u8 ($port-info p) (char->integer (string-ref str start)))
1)))]
[flush
(lambda (who p)
(flush-output-port ($port-info p)))]
[clear-output
(lambda (who p)
(clear-output-port ($port-info p)))]
[close-port (lambda (who p) (flush-output-port ($port-info p)) (void))]
[port-position
(lambda (who p)
(port-position ($port-info p)))]
[set-port-position!
(lambda (who p x)
(set-port-position! ($port-info p) x))]
[port-length
(lambda (who p)
(port-length ($port-info p)))]
[set-port-length!
(lambda (who p x)
(set-port-length! ($port-info p) x))]
[port-nonblocking? #f]
[set-port-nonblocking! #f]))
(set! $console-input-port ($make-textual-input-port "debug-stdin" debug-port-handler "" (standard-input-port (buffer-mode block))))
(set! $console-output-port ($make-textual-output-port "debug-stdout" debug-port-handler "" (standard-output-port (buffer-mode none))))
(set! $console-output-port ($make-textual-output-port "debug-stderr" debug-port-handler "" (standard-error-port (buffer-mode none)))))
(let ([ip (standard-input-port (buffer-mode block) (current-transcoder))]
[op (standard-output-port (buffer-mode line) (current-transcoder))])
(define same-device? (foreign-procedure "(cs)same_devicep" (int int) boolean))
(if-feature pthreads
(let ()
; it would be nice to make port->thread-safe-port available generally,
; but since it grabs the tc mutex, making it public would be
; inappropriate. tried using a fresh mutex, but the thread mat
; that runs compile-file freezes, possibly due to a deadlock where one
; thread has the tc mutex and another has the port's mutex. should
; revisit...
(define (make-thread-safe-handler ip op)
(make-port-handler
[ready?
(and ip
(lambda (who p)
(with-tc-mutex
(call-port-handler flush who op)
(call-port-handler ready? who ip))))]
[lookahead
(and ip
(lambda (who p)
(with-tc-mutex
(call-port-handler flush who op)
(call-port-handler lookahead who ip))))]
[unget
(and ip
(lambda (who p x)
(with-tc-mutex
(call-port-handler unget who ip x))))]
[get
(and ip
(lambda (who p)
(with-tc-mutex
(call-port-handler flush who op)
(call-port-handler get who ip))))]
[get-some
(and ip
(lambda (who p str start count)
(with-tc-mutex
(call-port-handler flush who op)
(call-port-handler get-some who ip str start count))))]
[clear-input
(and ip
(lambda (who p)
(with-tc-mutex
(call-port-handler clear-input who ip))))]
[put
(and op
(lambda (who p x)
(with-tc-mutex
(call-port-handler put who op x)
(if ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol))
($reset-port-flags! p (constant port-flag-bol))))))]
[put-some
(and op
(lambda (who p str start count)
(with-tc-mutex
(let ([count (call-port-handler put-some who op str start count)])
(if ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol))
($reset-port-flags! p (constant port-flag-bol)))
count))))]
[flush
(and op
(lambda (who p)
(with-tc-mutex
(call-port-handler flush who op))))]
[clear-output
(and op
(lambda (who p)
(with-tc-mutex
(call-port-handler clear-output who op))))]
[close-port ; refuse to close console ports---just flush instead
(if op
(lambda (who p)
(with-tc-mutex
(call-port-handler flush who op)))
(lambda (who p)
(void)))]
[port-position #f]
[set-port-position! #f]
[port-length #f]
[set-port-length! #f]
[port-nonblocking? #f]
[set-port-nonblocking! #f]))
(define thread-safe-console-input/output-port
(lambda (name ip op)
(let ([p ($make-textual-input/output-port name (make-thread-safe-handler ip op) "" "" #f)])
(when ($port-flags-set? ip (constant port-flag-r6rs))
($set-port-flags! p (constant port-flag-r6rs)))
(when ($port-flags-set? ip (constant port-flag-fold-case))
($set-port-flags! p (constant port-flag-fold-case)))
(when ($port-flags-set? ip (constant port-flag-no-fold-case))
($set-port-flags! p (constant port-flag-no-fold-case)))
(when ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol)))
p)))
(define thread-safe-console-output-port
(lambda (name op)
(let ([p ($make-textual-output-port name (make-thread-safe-handler #f op) "" #f)])
(when ($textual-port-bol? op)
($set-port-flags! p (constant port-flag-bol)))
p)))
(let ([p (thread-safe-console-input/output-port "stdin/out" ip op)])
(set! $console-input-port p)
(set! $console-output-port p)
(set! $console-error-port
(if (same-device? 1 2)
p
(thread-safe-console-output-port "stderr" (standard-error-port (buffer-mode line) (current-transcoder)))))))
(begin
(set! $console-input-port ip)
(set! $console-output-port op)
(set! $console-error-port
(if (same-device? 1 2)
op
(standard-error-port (buffer-mode line) (current-transcoder)))))))
(current-input-port $console-input-port)
(current-output-port $console-output-port)
(current-error-port $console-error-port)
(set-who! console-input-port
(make-parameter
$console-input-port
(lambda (ip)
(unless (and (input-port? ip) (textual-port? ip))
($oops who "~s is not a textual input port" ip))
ip)))
(set-who! console-output-port
(make-parameter
$console-output-port
(lambda (op)
(unless (and (output-port? op) (textual-port? op))
($oops who "~s is not a textual output port" op))
op)))
(set-who! console-error-port
(make-parameter
$console-error-port
(lambda (op)
(unless (and (output-port? op) (textual-port? op))
($oops who "~s is not a textual output port" op))
op)))
(set! $io-init
(lambda ()
(clear-open-files)
; reregister the console ports
(register-open-file $console-input-port)
(register-open-file $console-output-port)
(unless (eq? $console-error-port $console-output-port)
(register-open-file $console-error-port))))
; utf8->string, etc., are in prims.ss, since they are used by
; foreign procedures argument and return values
)