diff options
Diffstat (limited to 'libguile/ports-internal.h')
-rw-r--r-- | libguile/ports-internal.h | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/libguile/ports-internal.h b/libguile/ports-internal.h new file mode 100644 index 000000000..4e0a72f95 --- /dev/null +++ b/libguile/ports-internal.h @@ -0,0 +1,378 @@ +/* ports-internal.h - internal-only declarations for ports. + + Copyright 2013,2018 + Free Software Foundation, Inc. + + This file is part of Guile. + + Guile is free software: you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Guile 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with Guile. If not, see + <https://www.gnu.org/licenses/>. */ + +#ifndef SCM_PORTS_INTERNAL +#define SCM_PORTS_INTERNAL + +#include <assert.h> +#include <iconv.h> +#include <string.h> + +#include "libguile/bytevectors.h" +#include "libguile/list.h" +#include "libguile/pairs.h" +#include "libguile/ports.h" +#include "libguile/vectors.h" + +typedef enum scm_t_port_type_flags { + /* Indicates that the port should be closed if it is garbage collected + while it is open. */ + SCM_PORT_TYPE_NEEDS_CLOSE_ON_GC = 1 << 0 +} scm_t_port_type_flags; + +/* port-type description. */ +struct scm_t_port_type +{ + char *name; + int (*print) (SCM exp, SCM port, scm_print_state *pstate); + + size_t (*c_read) (SCM port, SCM dst, size_t start, size_t count); + size_t (*c_write) (SCM port, SCM src, size_t start, size_t count); + SCM scm_read; + SCM scm_write; + + int (*read_wait_fd) (SCM port); + int (*write_wait_fd) (SCM port); + + scm_t_off (*seek) (SCM port, scm_t_off OFFSET, int WHENCE); + void (*close) (SCM port); + + void (*get_natural_buffer_sizes) (SCM port, size_t *read_size, + size_t *write_size); + int (*random_access_p) (SCM port); + + int (*input_waiting) (SCM port); + + void (*truncate) (SCM port, scm_t_off length); + + unsigned flags; + + /* GOOPS tomfoolery. */ + SCM input_class, output_class, input_output_class; +}; + +/* Port buffers. + + It's important to avoid calling into the kernel too many times. For + that reason we buffer the input and output, using "port buffer" + objects. Port buffers are represented as vectors containing the + buffer, two cursors, and a flag. The bytes in a read buffer are laid + out like this: + + |already read | not yet | invalid + | data | read | data + readbuf: #vu8(|r r r r r r r|u u u u u|x x x x x|) + ^buf ^cur ^end ^size(buf) + + Similarly for a write buffer: + + |already written | not yet | invalid + | data | written | data + writebuf: #vu8(|w w w w w w w w |u u u u u|x x x x x|) + ^buf ^cur ^end ^size(buf) + + We use the same port buffer data structure for both purposes. Port + buffers are implemented as their own object so that they can be + atomically swapped in or out of ports, and as Scheme vectors so they + can be manipulated from Scheme. */ + +enum scm_port_buffer_field { + SCM_PORT_BUFFER_FIELD_BYTEVECTOR, + SCM_PORT_BUFFER_FIELD_CUR, + SCM_PORT_BUFFER_FIELD_END, + SCM_PORT_BUFFER_FIELD_HAS_EOF_P, + SCM_PORT_BUFFER_FIELD_POSITION, + SCM_PORT_BUFFER_FIELD_COUNT +}; + +/* The port buffers are exposed to Scheme, which can mutate their + fields. We have to do dynamic checks to ensure that + potentially-malicious Scheme doesn't invalidate our invariants. + However these dynamic checks are slow, so we need to avoid them where + they are unnecessary. An unnecessary check is a check which has + already been performed, or one which would already be performed by + the time that memory is accessed. Given that the "can_take", + "can_put", or "can_putback" functions are eventually called before + any access to the buffer, we hoist the necessary type checks the + can_foo and size functions, and otherwise assume that the cur and end + values are inums within the right ranges. */ + +static inline SCM +scm_port_buffer_bytevector (SCM buf) +{ + return SCM_SIMPLE_VECTOR_REF (buf, SCM_PORT_BUFFER_FIELD_BYTEVECTOR); +} + +static inline SCM +scm_port_buffer_cur (SCM buf) +{ + return SCM_SIMPLE_VECTOR_REF (buf, SCM_PORT_BUFFER_FIELD_CUR); +} + +static inline void +scm_port_buffer_set_cur (SCM buf, SCM cur) +{ + SCM_SIMPLE_VECTOR_SET (buf, SCM_PORT_BUFFER_FIELD_CUR, cur); +} + +static inline SCM +scm_port_buffer_end (SCM buf) +{ + return SCM_SIMPLE_VECTOR_REF (buf, SCM_PORT_BUFFER_FIELD_END); +} + +static inline void +scm_port_buffer_set_end (SCM buf, SCM end) +{ + SCM_SIMPLE_VECTOR_SET (buf, SCM_PORT_BUFFER_FIELD_END, end); +} + +static inline SCM +scm_port_buffer_has_eof_p (SCM buf) +{ + return SCM_SIMPLE_VECTOR_REF (buf, SCM_PORT_BUFFER_FIELD_HAS_EOF_P); +} + +static inline void +scm_port_buffer_set_has_eof_p (SCM buf, SCM has_eof_p) +{ + SCM_SIMPLE_VECTOR_SET (buf, SCM_PORT_BUFFER_FIELD_HAS_EOF_P, + has_eof_p); +} + +/* The port position object is a pair that is referenced by the port. + To make things easier for Scheme port code, it is also referenced by + port buffers. */ +static inline SCM +scm_port_buffer_position (SCM buf) +{ + return SCM_SIMPLE_VECTOR_REF (buf, SCM_PORT_BUFFER_FIELD_POSITION); +} + +static inline SCM +scm_port_position_line (SCM position) +{ + return scm_car (position); +} + +static inline void +scm_port_position_set_line (SCM position, SCM line) +{ + scm_set_car_x (position, line); +} + +static inline SCM +scm_port_position_column (SCM position) +{ + return scm_cdr (position); +} + +static inline void +scm_port_position_set_column (SCM position, SCM column) +{ + scm_set_cdr_x (position, column); +} + +static inline size_t +scm_port_buffer_size (SCM buf) +{ + SCM bv = scm_port_buffer_bytevector (buf); + if (SCM_LIKELY (SCM_BYTEVECTOR_P (bv))) + return SCM_BYTEVECTOR_LENGTH (bv); + scm_misc_error (NULL, "invalid port buffer ~a", scm_list_1 (bv)); + return -1; +} + +static inline void +scm_port_buffer_reset (SCM buf) +{ + scm_port_buffer_set_cur (buf, SCM_INUM0); + scm_port_buffer_set_end (buf, SCM_INUM0); +} + +static inline void +scm_port_buffer_reset_end (SCM buf) +{ + scm_port_buffer_set_cur (buf, scm_from_size_t (scm_port_buffer_size (buf))); + scm_port_buffer_set_end (buf, scm_from_size_t (scm_port_buffer_size (buf))); +} + +static inline size_t +scm_port_buffer_can_take (SCM buf, size_t *cur_out) +{ + size_t cur, end; + cur = scm_to_size_t (scm_port_buffer_cur (buf)); + end = scm_to_size_t (scm_port_buffer_end (buf)); + if (end > scm_port_buffer_size (buf)) + scm_misc_error (NULL, "invalid port buffer ~a", scm_list_1 (buf)); + /* If something races and we end up with end < cur, signal the caller + to do a fill_input and centralize there. */ + *cur_out = cur; + return end < cur ? 0 : end - cur; +} + +static inline size_t +scm_port_buffer_can_put (SCM buf, size_t *end_out) +{ + size_t end = scm_to_size_t (scm_port_buffer_end (buf)); + if (end > scm_port_buffer_size (buf)) + scm_misc_error (NULL, "invalid port buffer ~a", scm_list_1 (buf)); + *end_out = end; + return scm_port_buffer_size (buf) - end; +} + +static inline size_t +scm_port_buffer_can_putback (SCM buf) +{ + size_t cur = scm_to_size_t (scm_port_buffer_cur (buf)); + if (cur > scm_port_buffer_size (buf)) + scm_misc_error (NULL, "invalid port buffer ~a", scm_list_1 (buf)); + return cur; +} + +static inline void +scm_port_buffer_did_take (SCM buf, size_t prev_cur, size_t count) +{ + scm_port_buffer_set_cur (buf, SCM_I_MAKINUM (prev_cur + count)); +} + +static inline void +scm_port_buffer_did_put (SCM buf, size_t prev_end, size_t count) +{ + scm_port_buffer_set_end (buf, SCM_I_MAKINUM (prev_end + count)); +} + +static inline const uint8_t * +scm_port_buffer_take_pointer (SCM buf, size_t cur) +{ + signed char *ret = SCM_BYTEVECTOR_CONTENTS (scm_port_buffer_bytevector (buf)); + return ((uint8_t *) ret) + cur; +} + +static inline uint8_t * +scm_port_buffer_put_pointer (SCM buf, size_t end) +{ + signed char *ret = SCM_BYTEVECTOR_CONTENTS (scm_port_buffer_bytevector (buf)); + return ((uint8_t *) ret) + end; +} + +static inline size_t +scm_port_buffer_take (SCM buf, uint8_t *dst, size_t count, + size_t cur, size_t avail) +{ + if (avail < count) + count = avail; + if (dst) + memcpy (dst, scm_port_buffer_take_pointer (buf, cur), count); + scm_port_buffer_did_take (buf, cur, count); + return count; +} + +static inline size_t +scm_port_buffer_put (SCM buf, const uint8_t *src, size_t count, + size_t end, size_t avail) +{ + if (avail < count) + count = avail; + if (src) + memcpy (scm_port_buffer_put_pointer (buf, end), src, count); + scm_port_buffer_did_put (buf, end, count); + return count; +} + +static inline void +scm_port_buffer_putback (SCM buf, const uint8_t *src, size_t count, + size_t cur) +{ + assert (count <= cur); + + /* Sometimes used to move around data within a buffer, so we must use + memmove. */ + cur -= count; + scm_port_buffer_set_cur (buf, scm_from_size_t (cur)); + memmove (SCM_BYTEVECTOR_CONTENTS (scm_port_buffer_bytevector (buf)) + cur, + src, count); +} + +struct scm_t_port +{ + /* Source location information. */ + SCM file_name; + SCM position; + + /* Port buffers. */ + SCM read_buf; + SCM write_buf; + SCM write_buf_aux; + + /* All ports have read and write buffers; an unbuffered port simply + has a one-byte buffer. However unreading bytes can expand the read + buffer, but that doesn't mean that we want to increase the input + buffering. For that reason `read_buffering' is a separate + indication of how many characters to buffer on the read side. + There isn't a write_buf_size because there isn't an + `unwrite-byte'. */ + size_t read_buffering; + + /* Reads and writes can proceed concurrently, but we don't want to + start any read or write after close() has been called. So we have + a refcount which is positive if close has not yet been called. + Reading, writing, and the like temporarily increments this + refcount, provided it was nonzero to start with. */ + uint32_t refcount; + + /* True if the port is random access. Implies that the buffers must + be flushed before switching between reading and writing, seeking, + and so on. */ + uint32_t rw_random : 1; + uint32_t at_stream_start_for_bom_read : 1; + uint32_t at_stream_start_for_bom_write : 1; + + /* Character encoding support. */ + SCM encoding; /* A symbol of upper-case ASCII. */ + SCM conversion_strategy; /* A symbol; either substitute, error, or escape. */ + + /* This is the same as pt->encoding, except if `encoding' is UTF-16 or + UTF-32, in which case this is UTF-16LE or a similar + byte-order-specialed version of UTF-16 or UTF-32. This is a + separate field from `encoding' because being just plain UTF-16 or + UTF-32 has an additional meaning, being that we should consume and + produce byte order marker codepoints as appropriate. Set to #f + before the iconv descriptors have been opened. */ + SCM precise_encoding; /* with iconv_lock */ + iconv_t input_cd; /* with iconv_lock */ + iconv_t output_cd; /* with iconv_lock */ + + /* Port properties. */ + SCM alist; +}; + +#define SCM_UNICODE_BOM 0xFEFFUL /* Unicode byte-order mark */ + +#define SCM_FILENAME(x) (SCM_PORT (x)->file_name) +#define SCM_SET_FILENAME(x, n) (SCM_PORT (x)->file_name = (n)) + +SCM_INTERNAL void scm_port_acquire_iconv_descriptors (SCM port, + iconv_t *input_cd, + iconv_t *output_cd); +SCM_INTERNAL void scm_port_release_iconv_descriptors (SCM port); + +#endif |