Mercurial > hg > xemacs-beta
diff src/lstream.c @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ac2d302a0011 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lstream.c Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,1699 @@ +/* Generic stream implementation. + Copyright (C) 1995 Free Software Foundation, Inc. + Copyright (C) 1995 Sun Microsystems, Inc. + Copyright (C) 1996 Ben Wing. + +This file is part of XEmacs. + +XEmacs is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. + +XEmacs is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with XEmacs; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +/* Synched up with: Not in FSF. */ + +/* Written by Ben Wing. */ + +#include <config.h> +#include "lisp.h" + +#include "buffer.h" +#include "insdel.h" +#include "lstream.h" + +#include "sysfile.h" +#include <errno.h> + +/* This function provides a generic buffering stream implementation. + Conceptually, you send data to the stream or read data from the + stream, not caring what's on the other end of the stream. The + other end could be another stream, a file descriptor, a stdio + stream, a fixed block of memory, a reallocating block of memory, + etc. The main purpose of the stream is to provide a standard + interface and to do buffering. Macros are defined to read + or write characters, so the calling functions do not have to + worry about blocking data together in order to achieve efficiency. + */ + +/* Note that this object is called "stream" in Lisp but "lstream" + in C. The reason for this is that "stream" is too generic a name + for C; too much likelihood of conflict/confusion with C++, etc. */ + +/* Functions are as follows: + +Lstream *Lstream_new (Lstream_implementation *imp, CONST char *mode) + Allocate and return a new Lstream. This function is not + really meant to be called directly; rather, each stream type + should provide its own stream creation function, which + creates the stream and does any other necessary creation + stuff (e.g. opening a file). + +void Lstream_set_buffering (Lstream *lstr, Lstream_buffering buffering, + int buffering_size) + Change the buffering of a stream. See lstream.h. By default + the buffering is STREAM_BLOCK_BUFFERED. + +int Lstream_flush (Lstream *lstr) + Flush out any pending unwritten data in the stream. Clear + any buffered input data. Returns 0 on success, -1 on error. + +int Lstream_putc (Lstream *stream, int c) + Write out one byte to the stream. This is a macro and so + it is very efficient. The C argument is only evaluated once + but the STREAM argument is evaluated more than once. Returns + 0 on success, -1 on error. + +int Lstream_getc (Lstream *stream) + Read one byte from the stream. This is a macro and so it + is very efficient. The STREAM argument is evaluated more + than once. Return value is -1 for EOF or error. + +void Lstream_ungetc (Lstream *stream, int c) + Push one byte back onto the input queue. This will be the + next byte read from the stream. Any number of bytes can be + pushed back and will be read in the reverse order they were + pushed back -- most recent first. (This is necessary for + consistency -- if there are a number of bytes that have been + unread and I read and unread a byte, it needs to be the first + to be read again.) This is a macro and so it is very + efficient. The C argument is only evaluated once but the + STREAM argument is evaluated more than once. + +int Lstream_fputc (Lstream *stream, int c) +int Lstream_fgetc (Lstream *stream) +void Lstream_fungetc (Lstream *stream, int c) + Function equivalents of the above macros. + +int Lstream_read (Lstream *stream, void *data, int size) + Read SIZE bytes of DATA from the stream. Return the number of + bytes read. 0 means EOF. -1 means an error occurred and no + bytes were read. + +int Lstream_write (Lstream *stream, void *data, int size) + Write SIZE bytes of DATA to the stream. Return the number of + bytes written. -1 means an error occurred and no bytes were + written. + +void Lstream_unread (Lstream *stream, void *data, int size) + Push back SIZE bytes of DATA onto the input queue. The + next call to Lstream_read() with the same size will read the + same bytes back. Note that this will be the case even if + there is other pending unread data. + +int Lstream_close (Lstream *stream) + Close the stream. All data will be flushed out. + +void Lstream_reopen (Lstream *stream) + Reopen a closed stream. This enables I/O on it again. + This is not meant to be called except from a wrapper routine + that reinitializes variables and such -- the close routine + may well have freed some necessary storage structures, for + example. + +void Lstream_rewind (Lstream *stream) + Rewind the stream to the beginning. +*/ + +MAC_DEFINE (struct lstream *, MTlstream_data) + +static Lisp_Object mark_lstream (Lisp_Object, void (*) (Lisp_Object)); +static void print_lstream (Lisp_Object obj, Lisp_Object printcharfun, + int escapeflag); +static void finalize_lstream (void *header, int for_disksave); +static unsigned int sizeof_lstream (CONST void *header); +DEFINE_LRECORD_SEQUENCE_IMPLEMENTATION ("stream", lstream, + mark_lstream, print_lstream, + finalize_lstream, 0, 0, + sizeof_lstream, Lstream); + +#define DEFAULT_BLOCK_BUFFERING_SIZE 512 +#define MAX_READ_SIZE 512 + +static Lisp_Object +mark_lstream (Lisp_Object obj, void (*markobj) (Lisp_Object)) +{ + Lstream *lstr = XLSTREAM (obj); + if (lstr->imp->marker) + return (lstr->imp->marker) (obj, markobj); + else + return Qnil; +} + +static void +print_lstream (Lisp_Object obj, Lisp_Object printcharfun, int escapeflag) +{ + Lstream *lstr = XLSTREAM (obj); + char buf[200]; + + sprintf (buf, "#<INTERNAL EMACS BUG (%s lstream) 0x%x>", + lstr->imp->name, (EMACS_INT) lstr); + write_c_string (buf, printcharfun); +} + +static void +finalize_lstream (void *header, int for_disksave) +{ + /* WARNING WARNING WARNING. This function (and all finalize functions) + may get called more than once on the same object, and may get called + (at dump time) on objects that are not being released. */ + Lstream *lstr = (Lstream *) header; + +#if 0 /* this may cause weird Broken Pipes? */ + if (for_disksave) + { + Lstream_pseudo_close (lstr); + return; + } +#endif + if (lstr->flags & LSTREAM_FL_IS_OPEN) + Lstream_close (lstr); +} + +static unsigned int +sizeof_lstream (CONST void *header) +{ + CONST Lstream *lstr = (CONST Lstream *) header; + return sizeof (*lstr) + lstr->imp->size - 1; +} + +void +Lstream_set_buffering (Lstream *lstr, Lstream_buffering buffering, + int buffering_size) +{ + lstr->buffering = buffering; + switch (buffering) + { + case LSTREAM_UNBUFFERED: + lstr->buffering_size = 0; break; + case LSTREAM_BLOCK_BUFFERED: + lstr->buffering_size = DEFAULT_BLOCK_BUFFERING_SIZE; break; + case LSTREAM_BLOCKN_BUFFERED: + lstr->buffering_size = buffering_size; break; + case LSTREAM_LINE_BUFFERED: + case LSTREAM_UNLIMITED: + lstr->buffering_size = INT_MAX; break; + } +} + +static CONST Lstream_implementation *lstream_types[32]; +static Lisp_Object Vlstream_free_list[32]; +static int lstream_type_count; + +Lstream * +Lstream_new (CONST Lstream_implementation *imp, CONST char *mode) +{ + Lstream *p; + int i; + + for (i = 0; i < lstream_type_count; i++) + { + if (lstream_types[i] == imp) + break; + } + + if (i == lstream_type_count) + { + assert (lstream_type_count < countof (lstream_types)); + lstream_types[lstream_type_count] = imp; + Vlstream_free_list[lstream_type_count] = + make_lcrecord_list (sizeof (*p) + imp->size - 1, + lrecord_lstream); + lstream_type_count++; + } + + p = XLSTREAM (allocate_managed_lcrecord (Vlstream_free_list[i])); + /* Zero it out, except the header. */ + memset ((char *) p + sizeof (p->header), 0, + sizeof (*p) - sizeof (p->header) + imp->size - 1); + p->imp = imp; + Lstream_set_buffering (p, LSTREAM_BLOCK_BUFFERED, 0); + p->flags = LSTREAM_FL_IS_OPEN; + + /* convert mode (one of "r", "w", "rc", "wc") to p->flags */ + assert (mode[0] == 'r' || mode[0] == 'w'); + assert (mode[1] == 'c' || mode[1] == '\0'); + p->flags |= (mode[0] == 'r' ? LSTREAM_FL_READ : LSTREAM_FL_WRITE); + if (mode[1] == 'c') + p->flags |= LSTREAM_FL_NO_PARTIAL_CHARS; + + return p; +} + +void +Lstream_set_character_mode (Lstream *lstr) +{ + lstr->flags |= LSTREAM_FL_NO_PARTIAL_CHARS; +} + +void +Lstream_delete (Lstream *lstr) +{ + int i; + Lisp_Object val = Qnil; + + XSETLSTREAM (val, lstr); + for (i = 0; i < lstream_type_count; i++) + { + if (lstream_types[i] == lstr->imp) + { + free_managed_lcrecord (Vlstream_free_list[i], val); + return; + } + } + + abort (); +} + +static void +signal_simple_internal_error (CONST char *reason, Lisp_Object obj) +{ +#ifdef DEBUG_XEMACS + abort (); +#else + signal_simple_error (reason, obj); +#endif +} + +void +Lstream_reopen (Lstream *lstr) +{ + if (lstr->flags & LSTREAM_FL_IS_OPEN) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream already open", obj); + } + lstr->flags |= LSTREAM_FL_IS_OPEN; +} + +/* Attempt to flush out all of the buffered data for writing. */ + +int +Lstream_flush_out (Lstream *lstr) +{ + int num_written; + + while (lstr->out_buffer_ind > 0) + { + int size = lstr->out_buffer_ind; + if (! (lstr->flags & LSTREAM_FL_IS_OPEN)) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream not open", obj); + } + if (! (lstr->flags & LSTREAM_FL_WRITE)) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream not open for writing", obj); + } + if (!lstr->imp->writer) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream has no writer", obj); + } + + if (lstr->flags & LSTREAM_FL_NO_PARTIAL_CHARS) + /* It's quite possible for us to get passed an incomplete + character at the end. We need to spit back that + incomplete character. */ + { + CONST unsigned char *data = lstr->out_buffer; + CONST unsigned char *dataend = data + size - 1; + assert (size > 0); /* safety check ... */ + /* Optimize the most common case. */ + if (!BYTE_ASCII_P (*dataend)) + { + /* Go back to the beginning of the last (and possibly partial) + character, and bump forward to see if the character is + complete. */ + VALIDATE_CHARPTR_BACKWARD (dataend); + if (dataend + REP_BYTES_BY_FIRST_BYTE (*dataend) != data + size) + /* If not, chop the size down to ignore the last char + and stash it away for next time. */ + size = dataend - data; + /* If we don't even have one character to write, then just + skip out. */ + if (size == 0) + break; + } + } + + num_written = + (lstr->imp->writer) (lstr, lstr->out_buffer, size); + if (num_written == 0) + /* If nothing got written, then just hold the data. This may + occur, for example, if this stream does non-blocking I/O; + the attempt to write the data might have resulted in an + EWOULDBLOCK error. */ + return 0; + else if (num_written >= lstr->out_buffer_ind) + lstr->out_buffer_ind = 0; + else if (num_written > 0) + { + memmove (lstr->out_buffer, lstr->out_buffer + num_written, + lstr->out_buffer_ind - num_written); + lstr->out_buffer_ind -= num_written; + } + else + /* If error, just hold the data, for similar reasons as above. */ + return -1; + } + + if (lstr->imp->flusher) + return (lstr->imp->flusher) (lstr); + + return 0; +} + +int +Lstream_flush (Lstream *lstr) +{ + if (Lstream_flush_out (lstr) < 0) + return -1; + + /* clear out buffered data */ + lstr->in_buffer_current = lstr->in_buffer_ind = 0; + lstr->unget_buffer_ind = 0; + + return 0; +} + +/* We want to add NUM characters. This function ensures that the + buffer is large enough for this (per the buffering size specified + in the stream) and returns the number of characters we can + actually write. If FORCE is set, ignore the buffering size + and go ahead and make space for all the chars even if it exceeds + the buffering size. (This is used to deal with the possibility + that the stream writer might refuse to write any bytes now, e.g. + if it's getting EWOULDBLOCK errors. We have to keep stocking them + up until they can be written, so as to avoid losing data. */ + +static int +Lstream_adding (Lstream *lstr, int num, int force) +{ + /* Compute the size that the outbuffer needs to be after the + chars are added. */ + int size_needed = max (lstr->out_buffer_size, + num + lstr->out_buffer_ind); + /* Maybe chop it down so that we don't buffer more characters + than our advertised buffering size. */ + if (!force) + size_needed = min (lstr->buffering_size, size_needed); + DO_REALLOC (lstr->out_buffer, lstr->out_buffer_size, + size_needed, unsigned char); + /* There might be more data buffered than the buffering size, + so make sure we don't return a negative number here. */ + return max (0, min (num, size_needed - lstr->out_buffer_ind)); +} + +/* Like Lstream_write(), but does not handle line-buffering correctly. */ + +static int +Lstream_write_1 (Lstream *lstr, CONST void *data, int size) +{ + CONST unsigned char *p = data; + int off = 0; + if (! (lstr->flags & LSTREAM_FL_IS_OPEN)) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream not open", obj); + } + if (! (lstr->flags & LSTREAM_FL_WRITE)) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream not open for writing", obj); + } + { + int couldnt_write_last_time = 0; + + while (1) + { + /* Figure out how much we can add to the buffer */ + int chunk = Lstream_adding (lstr, size, 0); + if (chunk == 0) + { + if (couldnt_write_last_time) + /* Ung, we ran out of space and tried to flush + the buffer, but it didn't work because the stream + writer is refusing to accept any data. So we + just have to squirrel away all the rest of the + stuff. */ + chunk = Lstream_adding (lstr, size, 1); + else + couldnt_write_last_time = 1; + } + /* Do it. */ + if (chunk > 0) + { + memcpy (lstr->out_buffer + lstr->out_buffer_ind, p + off, chunk); + lstr->out_buffer_ind += chunk; + lstr->byte_count += chunk; + size -= chunk; + off += chunk; + } + /* If the buffer is full and we have more to add, flush it out. */ + if (size > 0) + { + if (Lstream_flush_out (lstr) < 0) + { + if (off == 0) + return -1; + else + return off; + } + } + else + break; + } + } + return off; +} + +/* If the stream is not line-buffered, then we can just call + Lstream_write_1(), which writes in chunks. Otherwise, we + repeatedly call Lstream_putc(), which knows how to handle + line buffering. */ + +int +Lstream_write (Lstream *lstr, CONST void *data, int size) +{ + int i; + CONST unsigned char *p = data; + + assert (size >= 0); + if (size == 0) + return size; + if (lstr->buffering != LSTREAM_LINE_BUFFERED) + return Lstream_write_1 (lstr, data, size); + for (i = 0; i < size; i++) + { + if (Lstream_putc (lstr, p[i]) < 0) + break; + } + return i == 0 ? -1 : 0; +} + +static int +Lstream_raw_read (Lstream *lstr, unsigned char *buffer, int size) +{ + if (! (lstr->flags & LSTREAM_FL_IS_OPEN)) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream not open", obj); + } + if (! (lstr->flags & LSTREAM_FL_READ)) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream not open for reading", obj); + } + if (!lstr->imp->reader) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream has no reader", obj); + } + + return (lstr->imp->reader) (lstr, buffer, size); +} + +/* Assuming the buffer is empty, fill it up again. */ + +static int +Lstream_read_more (Lstream *lstr) +{ +#if 0 + int size_needed = max (1, min (MAX_READ_SIZE, lstr->buffering_size)); +#else + /* If someone requested a larger buffer size, so be it! */ + int size_needed = max (1, lstr->buffering_size); +#endif + int size_gotten; + + DO_REALLOC (lstr->in_buffer, lstr->in_buffer_size, + size_needed, unsigned char); + size_gotten = Lstream_raw_read (lstr, lstr->in_buffer, size_needed); + lstr->in_buffer_current = max (0, size_gotten); + lstr->in_buffer_ind = 0; + return size_gotten < 0 ? -1 : size_gotten; +} + +int +Lstream_read (Lstream *lstr, void *data, int size) +{ + unsigned char *p = (unsigned char *) data; + int off = 0; + int chunk; + int error_occurred = 0; + + assert (size >= 0); + if (size == 0) + return 0; + + /* First try to get some data from the unget buffer */ + chunk = min (size, lstr->unget_buffer_ind); + if (chunk > 0) + { + /* The bytes come back in reverse order. */ + for (; off < chunk; off++) + p[off] = lstr->unget_buffer[--lstr->unget_buffer_ind]; + lstr->byte_count += chunk; + size -= chunk; + } + + while (size > 0) + { + /* Take whatever we can from the in buffer */ + chunk = min (size, lstr->in_buffer_current - lstr->in_buffer_ind); + if (chunk > 0) + { + memcpy (p + off, lstr->in_buffer + lstr->in_buffer_ind, chunk); + lstr->in_buffer_ind += chunk; + lstr->byte_count += chunk; + size -= chunk; + off += chunk; + } + + /* If we need some more, try to get some more from the stream's end */ + if (size > 0) + { + int retval = Lstream_read_more (lstr); + if (retval < 0) + error_occurred = 1; + if (retval <= 0) + break; + } + } + + /* #### Beware of OFF ending up 0. */ + if ((lstr->flags & LSTREAM_FL_NO_PARTIAL_CHARS) && off > 0) + { + /* It's quite possible for us to get passed an incomplete + character at the end. We need to spit back that + incomplete character. */ + CONST unsigned char *dataend = p + off - 1; + /* Optimize the most common case. */ + if (!BYTE_ASCII_P (*dataend)) + { + /* Go back to the beginning of the last (and possibly partial) + character, and bump forward to see if the character is + complete. */ + VALIDATE_CHARPTR_BACKWARD (dataend); + if (dataend + REP_BYTES_BY_FIRST_BYTE (*dataend) != p + off) + { + int newoff = dataend - p; + /* If not, chop the size down to ignore the last char + and stash it away for next time. */ + Lstream_unread (lstr, dataend, off - newoff); + off = newoff; + } + } + } + + return ((off == 0 && error_occurred) ? -1 : off); +} + +void +Lstream_unread (Lstream *lstr, CONST void *data, int size) +{ + int i; + unsigned char *p = (unsigned char *) data; + + /* Make sure buffer is big enough */ + + DO_REALLOC (lstr->unget_buffer, lstr->unget_buffer_size, + lstr->unget_buffer_ind + size, unsigned char); + + /* Bytes have to go on in reverse order -- they are reversed + again when read back. */ + for (i = size - 1; i >= 0; i--) + lstr->unget_buffer[lstr->unget_buffer_ind++] = p[i]; + lstr->byte_count -= size; +} + +int +Lstream_rewind (Lstream *lstr) +{ + if (!lstr->imp->rewinder) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream has no rewinder", obj); + } + if (Lstream_flush (lstr) < 0) + return -1; + lstr->byte_count = 0; + return (lstr->imp->rewinder) (lstr); +} + +int +Lstream_seekable_p (Lstream *lstr) +{ + if (!lstr->imp->rewinder) + return 0; + if (!lstr->imp->seekable_p) + return 1; + return (lstr->imp->seekable_p) (lstr); +} + +static int +Lstream_pseudo_close (Lstream *lstr) +{ + if (!lstr->flags & LSTREAM_FL_IS_OPEN) + { + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, lstr); + signal_simple_internal_error + ("Internal error: lstream is not open", obj); + } + + /* don't check errors here -- best not to risk file descriptor loss */ + Lstream_flush (lstr); + + return 0; +} + +int +Lstream_close (Lstream *lstr) +{ + if (lstr->flags & LSTREAM_FL_IS_OPEN) + { + /* don't return here on error, or file descriptor leak will result. */ + Lstream_pseudo_close (lstr); + if (lstr->imp->closer) + { + if ((lstr->imp->closer) (lstr) < 0) + return -1; + } + } + + lstr->flags &= ~LSTREAM_FL_IS_OPEN; + lstr->byte_count = 0; + /* Note that Lstream_flush() reset all the buffer indices. That way, + the next call to Lstream_putc(), Lstream_getc(), or Lstream_ungetc() + on a closed stream will call into the function equivalents, which will + cause an error. */ + + /* We set the pointers to 0 so that we don't lose when this function + is called more than once on the same object */ + if (lstr->out_buffer) + { + xfree (lstr->out_buffer); + lstr->out_buffer = 0; + } + if (lstr->in_buffer) + { + xfree (lstr->in_buffer); + lstr->in_buffer = 0; + } + if (lstr->unget_buffer) + { + xfree (lstr->unget_buffer); + lstr->unget_buffer = 0; + } + + return 0; +} + +int +Lstream_fputc (Lstream *lstr, int c) +{ + unsigned char ch = (unsigned char) c; + int retval = Lstream_write_1 (lstr, &ch, 1); + if (retval >= 0 && lstr->buffering == LSTREAM_LINE_BUFFERED && ch == '\n') + return Lstream_flush_out (lstr); + return retval < 0 ? -1 : 0; +} + +int +Lstream_fgetc (Lstream *lstr) +{ + unsigned char ch; + if (Lstream_read (lstr, &ch, 1) <= 0) + return -1; + return ch; +} + +void +Lstream_fungetc (Lstream *lstr, int c) +{ + unsigned char ch = (unsigned char) c; + Lstream_unread (lstr, &ch, 1); +} + + +/************************ some stream implementations *********************/ + +/*********** a stdio stream ***********/ + +struct stdio_stream +{ + FILE *file; + int closing; +}; + +#define STDIO_STREAM_DATA(stream) LSTREAM_TYPE_DATA (stream, stdio) + +DEFINE_LSTREAM_IMPLEMENTATION ("stdio", lstream_stdio, + sizeof (struct stdio_stream)); + +static Lisp_Object +make_stdio_stream_1 (FILE *stream, int flags, CONST char *mode) +{ + Lisp_Object obj = Qnil; + Lstream *lstr = Lstream_new (lstream_stdio, mode); + struct stdio_stream *str = STDIO_STREAM_DATA (lstr); + str->file = stream; + str->closing = flags & LSTR_CLOSING; + XSETLSTREAM (obj, lstr); + return obj; +} + +Lisp_Object +make_stdio_input_stream (FILE *stream, int flags) +{ + return make_stdio_stream_1 (stream, flags, "r"); +} + +Lisp_Object +make_stdio_output_stream (FILE *stream, int flags) +{ + return make_stdio_stream_1 (stream, flags, "w"); +} + +static int +stdio_reader (Lstream *stream, unsigned char *data, int size) +{ + struct stdio_stream *str = STDIO_STREAM_DATA (stream); + size_t val = fread (data, 1, (size_t) size, str->file); + if (!val && ferror (str->file)) + return -1; + return (int) val; +} + +static int +stdio_writer (Lstream *stream, CONST unsigned char *data, int size) +{ + struct stdio_stream *str = STDIO_STREAM_DATA (stream); + size_t val = fwrite (data, 1, (size_t) size, str->file); + if (!val && ferror (str->file)) + return -1; + return (int) val; +} + +static int +stdio_rewinder (Lstream *stream) +{ + rewind (STDIO_STREAM_DATA (stream)->file); + return 0; +} + +static int +stdio_seekable_p (Lstream *stream) +{ + struct stdio_stream *str = STDIO_STREAM_DATA (stream); + { + struct stat lestat; + + if (fstat (fileno (str->file), &lestat) < 0) + return 0; + return S_ISREG (lestat.st_mode); + } +} + +static int +stdio_flusher (Lstream *stream) +{ + struct stdio_stream *str = STDIO_STREAM_DATA (stream); + if (stream->flags & LSTREAM_FL_WRITE) + return fflush (str->file); + else + /* call fpurge? Only exists on some systems. */ + return 0; +} + +static int +stdio_closer (Lstream *stream) +{ + struct stdio_stream *str = STDIO_STREAM_DATA (stream); + if (str->closing) + return fclose (str->file); + else + if (stream->flags & LSTREAM_FL_WRITE) + return fflush (str->file); + else + /* call fpurge? Only exists on some systems. */ + return 0; +} + +/*********** a file descriptor ***********/ + +struct filedesc_stream +{ + int fd; + int pty_max_bytes; + Bufbyte eof_char; + int starting_pos; + int current_pos; + int end_pos; + int chars_sans_newline; + int closing :1; + int allow_quit :1; + int blocked_ok :1; + int pty_flushing :1; + int blocking_error_p :1; +}; + +#define FILEDESC_STREAM_DATA(stream) LSTREAM_TYPE_DATA (stream, filedesc) + +DEFINE_LSTREAM_IMPLEMENTATION ("filedesc", lstream_filedesc, + sizeof (struct filedesc_stream)); + +/* Make a stream that reads from or writes to a file descriptor FILEDESC. + OFFSET is the offset from the *current* file pointer that the reading + should start at. COUNT is the number of bytes to be read (it is + ignored when writing); -1 for unlimited. */ +static Lisp_Object +make_filedesc_stream_1 (int filedesc, int offset, int count, int flags, + CONST char *mode) +{ + Lisp_Object obj = Qnil; + Lstream *lstr = Lstream_new (lstream_filedesc, mode); + struct filedesc_stream *fstr = FILEDESC_STREAM_DATA (lstr); + fstr->fd = filedesc; + fstr->closing = !!(flags & LSTR_CLOSING); + fstr->allow_quit = !!(flags & LSTR_ALLOW_QUIT); + fstr->blocked_ok = !!(flags & LSTR_BLOCKED_OK); + fstr->pty_flushing = !!(flags & LSTR_PTY_FLUSHING); + fstr->blocking_error_p = 0; + fstr->chars_sans_newline = 0; + fstr->starting_pos = lseek (filedesc, offset, SEEK_CUR); + if (fstr->starting_pos < 0) + fstr->current_pos = 0; + else + fstr->current_pos = fstr->starting_pos; + if (count < 0) + fstr->end_pos = -1; + else + fstr->end_pos = fstr->starting_pos + count; + XSETLSTREAM (obj, lstr); + return obj; +} + +Lisp_Object +make_filedesc_input_stream (int filedesc, int offset, int count, int flags) +{ + return make_filedesc_stream_1 (filedesc, offset, count, flags, "r"); +} + +Lisp_Object +make_filedesc_output_stream (int filedesc, int offset, int count, int flags) +{ + return make_filedesc_stream_1 (filedesc, offset, count, flags, "w"); +} + +static int +filedesc_reader (Lstream *stream, unsigned char *data, int size) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + if (str->end_pos >= 0) + size = min (size, str->end_pos - str->current_pos); + size = (str->allow_quit ? read_allowing_quit : read) (str->fd, data, size); + if (size > 0) + str->current_pos += size; + return size; +} + +static int +errno_would_block_p (int val) +{ +#ifdef EWOULDBLOCK + if (val == EWOULDBLOCK) + return 1; +#endif +#ifdef EAGAIN + if (val == EAGAIN) + return 1; +#endif + return 0; +} + +static int +filedesc_writer (Lstream *stream, CONST unsigned char *data, int size) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + int retval; + int need_newline = 0; + + /* This function would be simple if it were not for the blasted + PTY max-bytes stuff. Why the hell can't they just have written + the PTY drivers right so this problem doesn't exist? + + Maybe all the PTY crap here should be moved into another stream + that does nothing but periodically insert EOF's as necessary. */ + if (str->pty_flushing) + { + /* To make life easy, only send out one line at the most. */ + CONST unsigned char *ptr; + + ptr = memchr (data, '\n', size); + if (ptr) + need_newline = 1; + else + ptr = data + size; + if (ptr - data >= str->pty_max_bytes - str->chars_sans_newline) + { + ptr = data + str->pty_max_bytes - str->chars_sans_newline; + need_newline = 0; + } + size = ptr - data; + } + + /**** start of non-PTY-crap ****/ + if (size > 0) + retval = ((str->allow_quit ? write_allowing_quit : write) + (str->fd, data, size)); + else + retval = 0; + if (retval < 0 && errno_would_block_p (errno) && str->blocked_ok) + { + str->blocking_error_p = 1; + return 0; + } + str->blocking_error_p = 0; + if (retval < 0) + return retval; + /**** end non-PTY-crap ****/ + + if (str->pty_flushing) + { + str->chars_sans_newline += retval; + /* Note that a newline was not among the bytes written out. + Add to the number of non-newline bytes written out, + and flush with an EOF if necessary. Be careful to + keep track of write errors as we go along and look + out for EWOULDBLOCK. */ + if (str->chars_sans_newline >= str->pty_max_bytes) + { + int retval2 = ((str->allow_quit ? write_allowing_quit : write) + (str->fd, &str->eof_char, 1)); + if (retval2 > 0) + str->chars_sans_newline = 0; + else if (retval2 < 0) + { + /* Error writing the EOF char. If nothing got written, + then treat this as an error -- either return an error + condition or set the blocking-error flag. */ + if (retval == 0) + { + if (errno_would_block_p (errno) && str->blocked_ok) + { + str->blocking_error_p = 1; + return 0; + } + else + return retval2; + } + else + return retval; + } + } + } + + /* The need_newline flag is necessary because otherwise when the + first byte is a newline, we'd get stuck never writing anything + in pty-flushing mode. */ + if (need_newline) + { + Bufbyte nl = '\n'; + int retval2 = ((str->allow_quit ? write_allowing_quit : write) + (str->fd, &nl, 1)); + if (retval2 > 0) + { + str->chars_sans_newline = 0; + retval++; + } + else if (retval2 < 0) + { + /* Error writing the newline char. If nothing got written, + then treat this as an error -- either return an error + condition or set the blocking-error flag. */ + if (retval == 0) + { + if (errno_would_block_p (errno) && str->blocked_ok) + { + str->blocking_error_p = 1; + return 0; + } + else + return retval2; + } + else + return retval; + } + } + + return retval; +} + +static int +filedesc_rewinder (Lstream *stream) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + if (str->starting_pos < 0 || + lseek (FILEDESC_STREAM_DATA (stream)->fd, str->starting_pos, + SEEK_SET) == -1) + return -1; + else + { + str->current_pos = str->starting_pos; + return 0; + } +} + +static int +filedesc_seekable_p (Lstream *stream) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + if (str->starting_pos < 0) + return 0; + else + { + struct stat lestat; + + if (fstat (str->fd, &lestat) < 0) + return 0; + return S_ISREG (lestat.st_mode); + } +} + +static int +filedesc_closer (Lstream *stream) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + if (str->closing) + return close (str->fd); + else + return 0; +} + +void +filedesc_stream_set_pty_flushing (Lstream *stream, int pty_max_bytes, + Bufbyte eof_char) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + str->pty_max_bytes = pty_max_bytes; + str->eof_char = eof_char; +} + +int +filedesc_stream_was_blocked (Lstream *stream) +{ + struct filedesc_stream *str = FILEDESC_STREAM_DATA (stream); + return str->blocking_error_p; +} + +/*********** read from a Lisp string ***********/ + +#define LISP_STRING_STREAM_DATA(stream) LSTREAM_TYPE_DATA (stream, lisp_string) + +struct lisp_string_stream +{ + Lisp_Object obj; + Bytecount init_offset; + Bytecount offset, end; +}; + +DEFINE_LSTREAM_IMPLEMENTATION ("lisp-string", lstream_lisp_string, + sizeof (struct lisp_string_stream)); + +Lisp_Object +make_lisp_string_input_stream (Lisp_Object string, Bytecount offset, + Bytecount len) +{ + Lisp_Object obj = Qnil; + Lstream *lstr; + struct lisp_string_stream *str; + + CHECK_STRING (string); + if (len < 0) + len = string_length (XSTRING (string)) - offset; + assert (offset >= 0); + assert (len >= 0); + assert (offset + len <= string_length (XSTRING (string))); + + lstr = Lstream_new (lstream_lisp_string, "r"); + str = LISP_STRING_STREAM_DATA (lstr); + str->offset = offset; + str->end = offset + len; + str->init_offset = offset; + str->obj = string; + XSETLSTREAM (obj, lstr); + return obj; +} + +static int +lisp_string_reader (Lstream *stream, unsigned char *data, int size) +{ + struct lisp_string_stream *str = LISP_STRING_STREAM_DATA (stream); + /* Don't lose if the string shrank past us ... */ + Bytecount offset = min (str->offset, string_length (XSTRING (str->obj))); + Bufbyte *strstart = string_data (XSTRING (str->obj)); + Bufbyte *start = strstart + offset; + + /* ... or if someone changed the string and we ended up in the + middle of a character. */ + /* Being in the middle of a character is `normal' unless + LSTREAM_NO_PARTIAL_CHARS - mrb */ + if (stream->flags & LSTREAM_FL_NO_PARTIAL_CHARS) + VALIDATE_CHARPTR_BACKWARD (start); + offset = start - strstart; + size = min (size, str->end - offset); + assert (size >= 0); /* paranoia */ + memcpy (data, start, size); + str->offset = offset + size; + return size; +} + +static int +lisp_string_rewinder (Lstream *stream) +{ + struct lisp_string_stream *str = LISP_STRING_STREAM_DATA (stream); + int pos = str->init_offset; + if (pos > str->end) + pos = str->end; + /* Don't lose if the string shrank past us ... */ + pos = min (pos, string_length (XSTRING (str->obj))); + /* ... or if someone changed the string and we ended up in the + middle of a character. */ + { + Bufbyte *strstart = string_data (XSTRING (str->obj)); + Bufbyte *start = strstart + pos; + VALIDATE_CHARPTR_BACKWARD (start); + pos = start - strstart; + } + str->offset = pos; + return 0; +} + +static Lisp_Object +lisp_string_marker (Lisp_Object stream, void (*markobj) (Lisp_Object)) +{ + struct lisp_string_stream *str = LISP_STRING_STREAM_DATA (XLSTREAM (stream)); + return str->obj; +} + +/*********** a fixed buffer ***********/ + +#define FIXED_BUFFER_STREAM_DATA(stream) \ + LSTREAM_TYPE_DATA (stream, fixed_buffer) + +struct fixed_buffer_stream +{ + CONST unsigned char *inbuf; + unsigned char *outbuf; + int size; + int offset; +}; + +DEFINE_LSTREAM_IMPLEMENTATION ("fixed-buffer", lstream_fixed_buffer, + sizeof (struct fixed_buffer_stream)); + +Lisp_Object +make_fixed_buffer_input_stream (CONST unsigned char *buf, int size) +{ + Lisp_Object obj = Qnil; + Lstream *lstr; + struct fixed_buffer_stream *str; + + lstr = Lstream_new (lstream_fixed_buffer, "r"); + str = FIXED_BUFFER_STREAM_DATA (lstr); + str->inbuf = buf; + str->size = size; + XSETLSTREAM (obj, lstr); + return obj; +} + +Lisp_Object +make_fixed_buffer_output_stream (unsigned char *buf, int size) +{ + Lisp_Object obj = Qnil; + Lstream *lstr; + struct fixed_buffer_stream *str; + + lstr = Lstream_new (lstream_fixed_buffer, "w"); + str = FIXED_BUFFER_STREAM_DATA (lstr); + str->outbuf = buf; + str->size = size; + XSETLSTREAM (obj, lstr); + return obj; +} + +static int +fixed_buffer_reader (Lstream *stream, unsigned char *data, int size) +{ + struct fixed_buffer_stream *str = FIXED_BUFFER_STREAM_DATA (stream); + size = min (size, str->size - str->offset); + memcpy (data, str->inbuf + str->offset, size); + str->offset += size; + return size; +} + +static int +fixed_buffer_writer (Lstream *stream, CONST unsigned char *data, int size) +{ + struct fixed_buffer_stream *str = FIXED_BUFFER_STREAM_DATA (stream); + if (str->offset == str->size) + { + /* If we're at the end, just throw away the data and pretend + we wrote all of it. If we return 0, then the lstream routines + will try again and again to write it out. */ + return size; + } + size = min (size, str->size - str->offset); + memcpy (str->outbuf + str->offset, data, size); + str->offset += size; + return size; +} + +static int +fixed_buffer_rewinder (Lstream *stream) +{ + FIXED_BUFFER_STREAM_DATA (stream)->offset = 0; + return 0; +} + +CONST unsigned char * +fixed_buffer_input_stream_ptr (Lstream *stream) +{ + assert (stream->imp == lstream_fixed_buffer); + return FIXED_BUFFER_STREAM_DATA (stream)->inbuf; +} + +unsigned char * +fixed_buffer_output_stream_ptr (Lstream *stream) +{ + assert (stream->imp == lstream_fixed_buffer); + return FIXED_BUFFER_STREAM_DATA (stream)->outbuf; +} + +/*********** write to a resizing buffer ***********/ + +#define RESIZING_BUFFER_STREAM_DATA(stream) \ + LSTREAM_TYPE_DATA (stream, resizing_buffer) + +struct resizing_buffer_stream +{ + unsigned char *buf; + int allocked; + int max_stored; + int stored; +}; + +DEFINE_LSTREAM_IMPLEMENTATION ("resizing-buffer", lstream_resizing_buffer, + sizeof (struct resizing_buffer_stream)); + +Lisp_Object +make_resizing_buffer_output_stream (void) +{ + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, Lstream_new (lstream_resizing_buffer, "w")); + return obj; +} + +static int +resizing_buffer_writer (Lstream *stream, CONST unsigned char *data, int size) +{ + struct resizing_buffer_stream *str = RESIZING_BUFFER_STREAM_DATA (stream); + DO_REALLOC (str->buf, str->allocked, str->stored + size, unsigned char); + memcpy (str->buf + str->stored, data, size); + str->stored += size; + str->max_stored = max (str->max_stored, str->stored); + return size; +} + +static int +resizing_buffer_rewinder (Lstream *stream) +{ + RESIZING_BUFFER_STREAM_DATA (stream)->stored = 0; + return 0; +} + +static int +resizing_buffer_closer (Lstream *stream) +{ + struct resizing_buffer_stream *str = RESIZING_BUFFER_STREAM_DATA (stream); + if (str->buf) + { + xfree (str->buf); + str->buf = 0; + } + return 0; +} + +unsigned char * +resizing_buffer_stream_ptr (Lstream *stream) +{ + return RESIZING_BUFFER_STREAM_DATA (stream)->buf; +} + +/*********** write to an unsigned-char dynarr ***********/ + +/* Note: If you have a dynarr whose type is not unsigned_char_dynarr + but which is really just an unsigned_char_dynarr (e.g. its type + is Bufbyte or Extbyte), just cast to unsigned_char_dynarr. */ + +#define DYNARR_STREAM_DATA(stream) \ + LSTREAM_TYPE_DATA (stream, dynarr) + +struct dynarr_stream +{ + unsigned_char_dynarr *dyn; +}; + +DEFINE_LSTREAM_IMPLEMENTATION ("dynarr", lstream_dynarr, + sizeof (struct dynarr_stream)); + +Lisp_Object +make_dynarr_output_stream (unsigned_char_dynarr *dyn) +{ + Lisp_Object obj = Qnil; + XSETLSTREAM (obj, Lstream_new (lstream_dynarr, "w")); + DYNARR_STREAM_DATA (XLSTREAM (obj))->dyn = dyn; + return obj; +} + +static int +dynarr_writer (Lstream *stream, CONST unsigned char *data, int size) +{ + struct dynarr_stream *str = DYNARR_STREAM_DATA (stream); + Dynarr_add_many (str->dyn, data, size); + return size; +} + +static int +dynarr_rewinder (Lstream *stream) +{ + Dynarr_reset (DYNARR_STREAM_DATA (stream)->dyn); + return 0; +} + +static int +dynarr_closer (Lstream *stream) +{ + return 0; +} + +/************ read from or write to a Lisp buffer ************/ + +/* Note: Lisp-buffer read streams never return partial characters, + and Lisp-buffer write streams expect to never get partial + characters. */ + +#define LISP_BUFFER_STREAM_DATA(stream) \ + LSTREAM_TYPE_DATA (stream, lisp_buffer) + +struct lisp_buffer_stream +{ + Lisp_Object buffer; + Lisp_Object orig_start; + /* we use markers to properly deal with insertion/deletion */ + Lisp_Object start, end; + int flags; +}; + +DEFINE_LSTREAM_IMPLEMENTATION ("lisp-buffer", lstream_lisp_buffer, + sizeof (struct lisp_buffer_stream)); + +static Lisp_Object +make_lisp_buffer_stream_1 (struct buffer *buf, Bufpos start, Bufpos end, + int flags, CONST char *mode) +{ + Lisp_Object obj = Qnil; + Lstream *lstr; + struct lisp_buffer_stream *str; + Bufpos bmin, bmax; + int reading = !strcmp (mode, "r"); + + /* Make sure the luser didn't pass "w" in. */ + if (!strcmp (mode, "w")) + abort (); + + if (flags & LSTR_IGNORE_ACCESSIBLE) + { + bmin = BUF_BEG (buf); + bmax = BUF_Z (buf); + } + else + { + bmin = BUF_BEGV (buf); + bmax = BUF_ZV (buf); + } + + if (start == -1) + start = bmin; + if (end == -1) + end = bmax; + assert (bmin <= start); + assert (start <= bmax); + if (reading) + { + assert (bmin <= end); + assert (end <= bmax); + assert (start <= end); + } + + lstr = Lstream_new (lstream_lisp_buffer, mode); + str = LISP_BUFFER_STREAM_DATA (lstr); + { + Lisp_Object marker; + Lisp_Object buffer = Qnil; + + XSETBUFFER (buffer, buf); + marker = Fmake_marker (); + Fset_marker (marker, make_int (start), buffer); + str->start = marker; + marker = Fmake_marker (); + Fset_marker (marker, make_int (start), buffer); + str->orig_start = marker; + if (reading) + { + marker = Fmake_marker (); + Fset_marker (marker, make_int (end), buffer); + str->end = marker; + } + else + str->end = Qnil; + str->buffer = buffer; + } + str->flags = flags; + XSETLSTREAM (obj, lstr); + return obj; +} + +Lisp_Object +make_lisp_buffer_input_stream (struct buffer *buf, Bufpos start, Bufpos end, + int flags) +{ + return make_lisp_buffer_stream_1 (buf, start, end, flags, "r"); +} + +Lisp_Object +make_lisp_buffer_output_stream (struct buffer *buf, Bufpos pos, int flags) +{ + Lisp_Object lstr = make_lisp_buffer_stream_1 (buf, pos, 0, flags, "w"); + + Lstream_set_character_mode (XLSTREAM (lstr)); + return lstr; +} + +static int +lisp_buffer_reader (Lstream *stream, unsigned char *data, int size) +{ + struct lisp_buffer_stream *str = LISP_BUFFER_STREAM_DATA (stream); + unsigned char *orig_data = data; + Bytind start; + Bytind end; + struct buffer *buf = XBUFFER (str->buffer); + + if (!BUFFER_LIVE_P (buf)) + return 0; /* Fut. */ + + /* NOTE: We do all our operations in Bytind's. + Keep in mind that SIZE is a value in bytes, not chars. */ + + start = bi_marker_position (str->start); + end = bi_marker_position (str->end); + if (!(str->flags & LSTR_IGNORE_ACCESSIBLE)) + { + start = bytind_clip_to_bounds (BI_BUF_BEGV (buf), start, + BI_BUF_ZV (buf)); + end = bytind_clip_to_bounds (BI_BUF_BEGV (buf), end, + BI_BUF_ZV (buf)); + } + + size = min (size, end - start); + end = start + size; + /* We cannot return a partial character. */ + VALIDATE_BYTIND_BACKWARD (buf, end); + + while (start < end) + { + Bytind ceil; + Bytecount chunk; + + if (str->flags & LSTR_IGNORE_ACCESSIBLE) + ceil = BI_BUF_CEILING_OF_IGNORE_ACCESSIBLE (buf, start); + else + ceil = BI_BUF_CEILING_OF (buf, start); + chunk = min (ceil, end) - start; + memcpy (data, BI_BUF_BYTE_ADDRESS (buf, start), chunk); + data += chunk; + start += chunk; + } + + if (EQ (buf->selective_display, Qt) && str->flags & LSTR_SELECTIVE) + { + /* What a kludge. What a kludge. What a kludge. */ + unsigned char *p; + for (p = orig_data; p < data; p++) + if (*p == '\r') + *p = '\n'; + } + + set_bi_marker_position (str->start, end); + return data - orig_data; +} + +static int +lisp_buffer_writer (Lstream *stream, CONST unsigned char *data, int size) +{ + struct lisp_buffer_stream *str = LISP_BUFFER_STREAM_DATA (stream); + Bufpos pos; + struct buffer *buf = XBUFFER (str->buffer); + + if (!BUFFER_LIVE_P (buf)) + return 0; /* Fut. */ + + pos = marker_position (str->start); + pos += buffer_insert_raw_string_1 (buf, pos, data, size, 0); + set_marker_position (str->start, pos); + return size; +} + +static int +lisp_buffer_rewinder (Lstream *stream) +{ + struct lisp_buffer_stream *str = + LISP_BUFFER_STREAM_DATA (stream); + struct buffer *buf = XBUFFER (str->buffer); + long pos = marker_position (str->orig_start); + if (!BUFFER_LIVE_P (buf)) + return -1; /* Fut. */ + if (pos > BUF_ZV (buf)) + pos = BUF_ZV (buf); + if (pos < marker_position (str->orig_start)) + pos = marker_position (str->orig_start); + if (MARKERP (str->end) && pos > marker_position (str->end)) + pos = marker_position (str->end); + set_marker_position (str->start, pos); + return 0; +} + +static Lisp_Object +lisp_buffer_marker (Lisp_Object stream, void (*markobj) (Lisp_Object)) +{ + struct lisp_buffer_stream *str = + LISP_BUFFER_STREAM_DATA (XLSTREAM (stream)); + + (markobj) (str->start); + (markobj) (str->end); + return str->buffer; +} + +Bufpos +lisp_buffer_stream_startpos (Lstream *stream) +{ + return marker_position (LISP_BUFFER_STREAM_DATA (stream)->start); +} + + +/************************************************************************/ +/* initialization */ +/************************************************************************/ + +void +lstream_type_create (void) +{ + LSTREAM_HAS_METHOD (stdio, reader); + LSTREAM_HAS_METHOD (stdio, writer); + LSTREAM_HAS_METHOD (stdio, rewinder); + LSTREAM_HAS_METHOD (stdio, seekable_p); + LSTREAM_HAS_METHOD (stdio, flusher); + LSTREAM_HAS_METHOD (stdio, closer); + + LSTREAM_HAS_METHOD (filedesc, reader); + LSTREAM_HAS_METHOD (filedesc, writer); + LSTREAM_HAS_METHOD (filedesc, rewinder); + LSTREAM_HAS_METHOD (filedesc, seekable_p); + LSTREAM_HAS_METHOD (filedesc, closer); + + LSTREAM_HAS_METHOD (lisp_string, reader); + LSTREAM_HAS_METHOD (lisp_string, rewinder); + LSTREAM_HAS_METHOD (lisp_string, marker); + + LSTREAM_HAS_METHOD (fixed_buffer, reader); + LSTREAM_HAS_METHOD (fixed_buffer, writer); + LSTREAM_HAS_METHOD (fixed_buffer, rewinder); + + LSTREAM_HAS_METHOD (resizing_buffer, writer); + LSTREAM_HAS_METHOD (resizing_buffer, rewinder); + LSTREAM_HAS_METHOD (resizing_buffer, closer); + + LSTREAM_HAS_METHOD (dynarr, writer); + LSTREAM_HAS_METHOD (dynarr, rewinder); + LSTREAM_HAS_METHOD (dynarr, closer); + + LSTREAM_HAS_METHOD (lisp_buffer, reader); + LSTREAM_HAS_METHOD (lisp_buffer, writer); + LSTREAM_HAS_METHOD (lisp_buffer, rewinder); + LSTREAM_HAS_METHOD (lisp_buffer, marker); +} + +void +vars_of_lstream (void) +{ + int i; + + for (i = 0; i < countof (Vlstream_free_list); i++) + { + Vlstream_free_list[i] = Qnil; + staticpro (&Vlstream_free_list[i]); + } +}