Optimize `peek-char'.
[bpt/guile.git] / libguile / ports.c
index 627fd3f..6cf0de2 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 1995,1996,1997,1998,1999,2000,2001, 2003, 2004, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
+/* Copyright (C) 1995,1996,1997,1998,1999,2000,2001, 2003, 2004, 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
  * 
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public License
 #include <stdio.h>
 #include <errno.h>
 #include <fcntl.h>  /* for chsize on mingw */
+#include <assert.h>
+#include <uniconv.h>
+#include <unistr.h>
+#include <striconveh.h>
+
 #include <assert.h>
 
 #include "libguile/_scm.h"
 #include "libguile/async.h"
 #include "libguile/eval.h"
 #include "libguile/fports.h"  /* direct access for seek and truncate */
-#include "libguile/objects.h"
 #include "libguile/goops.h"
 #include "libguile/smob.h"
 #include "libguile/chars.h"
 #include "libguile/vectors.h"
 #include "libguile/weaks.h"
 #include "libguile/fluids.h"
+#include "libguile/eq.h"
 
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif
 
-#ifdef HAVE_MALLOC_H
-#include <malloc.h>
-#endif
-
 #ifdef HAVE_IO_H
 #include <io.h>
 #endif
@@ -79,7 +80,7 @@
    gnulib ftruncate.c has code using fcntl F_CHSIZE and F_FREESP, which
    might be possibilities if we've got other systems without ftruncate.  */
 
-#if HAVE_CHSIZE && ! HAVE_FTRUNCATE
+#if defined HAVE_CHSIZE && ! defined HAVE_FTRUNCATE
 #define ftruncate(fd, size) chsize (fd, size)
 #undef HAVE_FTRUNCATE
 #define HAVE_FTRUNCATE 1
@@ -94,8 +95,8 @@
  * Indexes into this table are used when generating type
  * tags for smobjects (if you know a tag you can get an index and conversely).
  */
-scm_t_ptob_descriptor *scm_ptobs;
-long scm_numptob;
+scm_t_ptob_descriptor *scm_ptobs = NULL;
+long scm_numptob = 0;
 
 /* GC marker for a port with stream of SCM type.  */
 SCM 
@@ -125,12 +126,6 @@ end_input_default (SCM port SCM_UNUSED, int offset SCM_UNUSED)
 {
 }
 
-static size_t
-scm_port_free0 (SCM port)
-{
-  return 0;
-}
-
 scm_t_bits
 scm_make_port_type (char *name,
                    int (*fill_input) (SCM port),
@@ -140,16 +135,18 @@ scm_make_port_type (char *name,
   if (SCM_I_MAX_PORT_TYPE_COUNT - 1 <= scm_numptob)
     goto ptoberr;
   SCM_CRITICAL_SECTION_START;
-  SCM_SYSCALL (tmp = (char *) realloc ((char *) scm_ptobs,
-                                      (1 + scm_numptob)
-                                      * sizeof (scm_t_ptob_descriptor)));
+  tmp = (char *) scm_gc_realloc ((char *) scm_ptobs,
+                                scm_numptob * sizeof (scm_t_ptob_descriptor),
+                                (1 + scm_numptob)
+                                * sizeof (scm_t_ptob_descriptor),
+                                "port-type");
   if (tmp)
     {
       scm_ptobs = (scm_t_ptob_descriptor *) tmp;
 
       scm_ptobs[scm_numptob].name = name;
       scm_ptobs[scm_numptob].mark = 0;
-      scm_ptobs[scm_numptob].free = scm_port_free0;
+      scm_ptobs[scm_numptob].free = NULL;
       scm_ptobs[scm_numptob].print = scm_port_print;
       scm_ptobs[scm_numptob].equalp = 0;
       scm_ptobs[scm_numptob].close = 0;
@@ -358,10 +355,10 @@ SCM_DEFINE (scm_drain_input, "drain-input", 1, 0, 0,
 \f
 /* Standard ports --- current input, output, error, and more(!).  */
 
-static SCM cur_inport_fluid;
-static SCM cur_outport_fluid;
-static SCM cur_errport_fluid;
-static SCM cur_loadport_fluid;
+static SCM cur_inport_fluid = 0;
+static SCM cur_outport_fluid = 0;
+static SCM cur_errport_fluid = 0;
+static SCM cur_loadport_fluid = 0;
 
 SCM_DEFINE (scm_current_input_port, "current-input-port", 0, 0, 0,
            (),
@@ -370,7 +367,10 @@ SCM_DEFINE (scm_current_input_port, "current-input-port", 0, 0, 0,
            "returns the @dfn{standard input} in Unix and C terminology.")
 #define FUNC_NAME s_scm_current_input_port
 {
-  return scm_fluid_ref (cur_inport_fluid);
+  if (cur_inport_fluid)
+    return scm_fluid_ref (cur_inport_fluid);
+  else
+    return SCM_BOOL_F;
 }
 #undef FUNC_NAME
 
@@ -382,7 +382,10 @@ SCM_DEFINE (scm_current_output_port, "current-output-port", 0, 0, 0,
            "Unix and C terminology.")
 #define FUNC_NAME s_scm_current_output_port
 {
-  return scm_fluid_ref (cur_outport_fluid);
+  if (cur_outport_fluid)
+    return scm_fluid_ref (cur_outport_fluid);
+  else
+    return SCM_BOOL_F;
 }
 #undef FUNC_NAME
 
@@ -392,7 +395,10 @@ SCM_DEFINE (scm_current_error_port, "current-error-port", 0, 0, 0,
            "@dfn{standard error} in Unix and C terminology).")
 #define FUNC_NAME s_scm_current_error_port
 {
-  return scm_fluid_ref (cur_errport_fluid);
+  if (cur_errport_fluid)
+    return scm_fluid_ref (cur_errport_fluid);
+  else
+    return SCM_BOOL_F;
 }
 #undef FUNC_NAME
 
@@ -496,8 +502,69 @@ SCM scm_i_port_weak_hash;
 
 scm_i_pthread_mutex_t scm_i_port_table_mutex = SCM_I_PTHREAD_MUTEX_INITIALIZER;
 
-/* This function is not and should not be thread safe. */
+\f
+/* Port finalization.  */
+
+
+static void finalize_port (GC_PTR, GC_PTR);
+
+/* Register a finalizer for PORT, if needed by its port type.  */
+static SCM_C_INLINE_KEYWORD void
+register_finalizer_for_port (SCM port)
+{
+  long port_type;
+
+  port_type = SCM_TC2PTOBNUM (SCM_CELL_TYPE (port));
+  if (scm_ptobs[port_type].free)
+    {
+      GC_finalization_proc prev_finalizer;
+      GC_PTR prev_finalization_data;
+
+      GC_REGISTER_FINALIZER_NO_ORDER (SCM2PTR (port), finalize_port, 0,
+                                     &prev_finalizer,
+                                     &prev_finalization_data);
+    }
+}
+
+/* Finalize the object (a port) pointed to by PTR.  */
+static void
+finalize_port (GC_PTR ptr, GC_PTR data)
+{
+  long port_type;
+  SCM port = PTR2SCM (ptr);
+
+  if (!SCM_PORTP (port))
+    abort ();
+
+  if (SCM_OPENP (port))
+    {
+      if (SCM_REVEALED (port) > 0)
+       /* Keep "revealed" ports alive and re-register a finalizer.  */
+       register_finalizer_for_port (port);
+      else
+       {
+         port_type = SCM_TC2PTOBNUM (SCM_CELL_TYPE (port));
+         if (port_type >= scm_numptob)
+           abort ();
+
+         if (scm_ptobs[port_type].free)
+           /* Yes, I really do mean `.free' rather than `.close'.  `.close'
+              is for explicit `close-port' by user.  */
+           scm_ptobs[port_type].free (port);
+
+         SCM_SETSTREAM (port, 0);
+         SCM_CLR_PORT_OPEN_FLAG (port);
+
+         scm_gc_ports_collected++;
+       }
+    }
+}
 
+
+
+\f
+
+/* This function is not and should not be thread safe. */
 SCM
 scm_new_port_table_entry (scm_t_bits tag)
 #define FUNC_NAME "scm_new_port_table_entry"
@@ -509,16 +576,28 @@ scm_new_port_table_entry (scm_t_bits tag)
   
   SCM z = scm_cons (SCM_EOL, SCM_EOL);
   scm_t_port *entry = (scm_t_port *) scm_gc_calloc (sizeof (scm_t_port), "port");
+  const char *enc;
 
   entry->file_name = SCM_BOOL_F;
   entry->rw_active = SCM_PORT_NEITHER;
   entry->port = z;
+  /* Initialize this port with the thread's current default
+     encoding.  */
+  if ((enc = scm_i_get_port_encoding (SCM_BOOL_F)) == NULL)
+    entry->encoding = NULL;
+  else
+    entry->encoding = scm_gc_strdup (enc, "port");
+  entry->ilseq_handler = scm_i_get_conversion_strategy (SCM_BOOL_F);
 
   SCM_SET_CELL_TYPE (z, tag);
   SCM_SETPTAB_ENTRY (z, entry);
 
   scm_hashq_set_x (scm_i_port_weak_hash, z, SCM_BOOL_F);
 
+  /* For each new port, register a finalizer so that it port type's free
+     function can be invoked eventually.  */
+  register_finalizer_for_port (z);
+
   return z;
 }
 #undef FUNC_NAME
@@ -547,9 +626,11 @@ scm_i_remove_port (SCM port)
 #define FUNC_NAME "scm_remove_port"
 {
   scm_t_port *p = SCM_PTAB_ENTRY (port);
-  if (p->putback_buf)
-    scm_gc_free (p->putback_buf, p->putback_buf_size, "putback buffer");
-  scm_gc_free (p, sizeof (scm_t_port), "port");
+
+  scm_port_non_buffer (p);
+
+  p->putback_buf = NULL;
+  p->putback_buf_size = 0;
 
   SCM_SETPTAB_ENTRY (port, 0);
   scm_hashq_remove_x (scm_i_port_weak_hash, port);
@@ -632,21 +713,22 @@ SCM_DEFINE (scm_set_port_revealed_x, "set-port-revealed!", 2, 0, 0,
  */
 
 static long
-scm_i_mode_bits_n (const char *modes, size_t n)
+scm_i_mode_bits_n (SCM modes)
 {
   return (SCM_OPN
-         | (memchr (modes, 'r', n) || memchr (modes, '+', n) ? SCM_RDNG : 0)
-         | (   memchr (modes, 'w', n)
-            || memchr (modes, 'a', n)
-            || memchr (modes, '+', n) ? SCM_WRTNG : 0)
-         | (memchr (modes, '0', n) ? SCM_BUF0 : 0)
-         | (memchr (modes, 'l', n) ? SCM_BUFLINE : 0));
+         | (scm_i_string_contains_char (modes, 'r') 
+            || scm_i_string_contains_char (modes, '+') ? SCM_RDNG : 0)
+         | (scm_i_string_contains_char (modes, 'w')
+            || scm_i_string_contains_char (modes, 'a')
+            || scm_i_string_contains_char (modes, '+') ? SCM_WRTNG : 0)
+         | (scm_i_string_contains_char (modes, '0') ? SCM_BUF0 : 0)
+         | (scm_i_string_contains_char (modes, 'l') ? SCM_BUFLINE : 0));
 }
 
 long
 scm_mode_bits (char *modes)
 {
-  return scm_i_mode_bits_n (modes, strlen (modes));
+  return scm_i_mode_bits (scm_from_locale_string (modes));
 }
 
 long
@@ -657,8 +739,7 @@ scm_i_mode_bits (SCM modes)
   if (!scm_is_string (modes))
     scm_wrong_type_arg_msg (NULL, 0, modes, "string");
 
-  bits = scm_i_mode_bits_n (scm_i_string_chars (modes),
-                           scm_i_string_length (modes));
+  bits = scm_i_mode_bits_n (modes);
   scm_remember_upto_here_1 (modes);
   return bits;
 }
@@ -929,7 +1010,7 @@ SCM_DEFINE (scm_read_char, "read-char", 0, 1, 0,
            "characters are available, the end-of-file object is returned.")
 #define FUNC_NAME s_scm_read_char
 {
-  int c;
+  scm_t_wchar c;
   if (SCM_UNBNDP (port))
     port = scm_current_input_port ();
   SCM_VALIDATE_OPINPORT (1, port);
@@ -940,6 +1021,154 @@ SCM_DEFINE (scm_read_char, "read-char", 0, 1, 0,
 }
 #undef FUNC_NAME
 
+#define SCM_MBCHAR_BUF_SIZE (4)
+
+/* Read a codepoint from PORT and return it.  Fill BUF with the byte
+   representation of the codepoint in PORT's encoding, and set *LEN to
+   the length in bytes of that representation.  Raise an error on
+   failure.  */
+static scm_t_wchar
+get_codepoint (SCM port, char buf[SCM_MBCHAR_BUF_SIZE], size_t *len)
+{
+  int c;
+  size_t bufcount = 0;
+  scm_t_uint32 result_buf;
+  scm_t_wchar codepoint = 0;
+  scm_t_uint32 *u32;
+  size_t u32len;
+  scm_t_port *pt = SCM_PTAB_ENTRY (port);
+
+  c = scm_get_byte_or_eof (port);
+  if (c == EOF)
+    return (scm_t_wchar) EOF;
+
+  buf[0] = c;
+  bufcount++;
+
+  if (pt->encoding == NULL)
+    {
+      /* The encoding is Latin-1: bytes are characters.  */
+      codepoint = (unsigned char) buf[0];
+      goto success;
+    }
+
+  for (;;)
+    {
+      u32len = sizeof (result_buf) / sizeof (scm_t_uint32);
+      u32 = u32_conv_from_encoding (pt->encoding,
+                                    (enum iconv_ilseq_handler) pt->ilseq_handler,
+                                   buf, bufcount, NULL, &result_buf, &u32len);
+      if (u32 == NULL || u32len == 0)
+       {
+         if (errno == ENOMEM)
+           scm_memory_error ("Input decoding");
+
+         /* Otherwise errno is EILSEQ or EINVAL, so perhaps more
+             bytes are needed.  Keep looping.  */
+       }
+      else
+       {
+         /* Complete codepoint found. */
+         codepoint = u32[0];
+
+         if (SCM_UNLIKELY (u32 != &result_buf))
+           /* libunistring up to 0.9.3 (included) would always heap-allocate
+              the result even when a large-enough RESULT_BUF is supplied, see
+              <http://lists.gnu.org/archive/html/bug-libunistring/2010-07/msg00003.html>.  */
+           free (u32);
+
+         goto success;
+       }
+
+      if (bufcount == SCM_MBCHAR_BUF_SIZE)
+       {
+         /* We've read several bytes and didn't find a good
+            codepoint.  Give up.  */
+         goto failure;
+       }
+
+      c = scm_get_byte_or_eof (port);
+
+      if (c == EOF)
+       {
+         /* EOF before a complete character was read.  Push it all
+            back and return EOF. */
+         while (bufcount > 0)
+           {
+             /* FIXME: this will probably cause errors in the port column. */
+             scm_unget_byte (buf[bufcount-1], port);
+             bufcount --;
+           }
+          return EOF;
+       }
+      
+      if (c == '\n')
+       {
+          /* It is always invalid to have EOL in the middle of a
+             multibyte character.  */
+         scm_unget_byte ('\n', port);
+         goto failure;
+       }
+       
+      buf[bufcount++] = c;
+    }
+
+ success:
+  switch (codepoint)
+    {
+    case '\a':
+      break;
+    case '\b':
+      SCM_DECCOL (port);
+      break;
+    case '\n':
+      SCM_INCLINE (port);
+        break;
+    case '\r':
+      SCM_ZEROCOL (port);
+      break;
+    case '\t':
+      SCM_TABCOL (port);
+      break;
+    default:
+      SCM_INCCOL (port);
+      break;
+    }
+
+  *len = bufcount;
+
+  return codepoint;
+
+ failure:
+  {
+    char *err_buf;
+    SCM err_str = scm_i_make_string (bufcount, &err_buf);
+    memcpy (err_buf, buf, bufcount);
+
+    if (errno == EILSEQ)
+      scm_misc_error (NULL, "input encoding error for ~s: ~s",
+                     scm_list_2 (scm_from_locale_string (scm_i_get_port_encoding (port)),
+                                 err_str));
+    else
+      scm_misc_error (NULL, "input encoding error (invalid) for ~s: ~s\n", 
+                     scm_list_2 (scm_from_locale_string (scm_i_get_port_encoding (port)),
+                                 err_str));
+  }
+
+  /* Never gets here.  */
+  return 0;
+}
+
+/* Read a codepoint from PORT and return it.  */
+scm_t_wchar
+scm_getc (SCM port)
+{
+  size_t len;
+  char buf[SCM_MBCHAR_BUF_SIZE];
+
+  return get_codepoint (port, buf, &len);
+}
+
 /* this should only be called when the read buffer is empty.  it
    tries to refill the read buffer.  it returns the first char from
    the port, which is either EOF or *(pt->read_pos).  */
@@ -969,7 +1198,24 @@ scm_fill_input (SCM port)
  * This function differs from scm_c_write; it updates port line and
  * column. */
 
-void 
+static void
+update_port_lf (scm_t_wchar c, SCM port)
+{
+  if (c == '\a')
+    ;                           /* Do nothing. */
+  else if (c == '\b')
+    SCM_DECCOL (port);
+  else if (c == '\n')
+    SCM_INCLINE (port);
+  else if (c == '\r')
+    SCM_ZEROCOL (port);
+  else if (c == '\t')
+    SCM_TABCOL (port);
+  else
+    SCM_INCCOL (port);
+}
+
+void
 scm_lfwrite (const char *ptr, size_t size, SCM port)
 {
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
@@ -980,25 +1226,74 @@ scm_lfwrite (const char *ptr, size_t size, SCM port)
 
   ptob->write (port, ptr, size);
 
-  for (; size; ptr++, size--) {
-    if (*ptr == '\a') {
-    }
-    else if (*ptr == '\b') {
-      SCM_DECCOL(port);
-    }
-    else if (*ptr == '\n') {
-      SCM_INCLINE(port);
-    }
-    else if (*ptr == '\r') {
-      SCM_ZEROCOL(port);
-    }
-    else if (*ptr == '\t') {
-      SCM_TABCOL(port);
+  for (; size; ptr++, size--)
+    update_port_lf ((scm_t_wchar) (unsigned char) *ptr, port);
+
+  if (pt->rw_random)
+    pt->rw_active = SCM_PORT_WRITE;
+}
+
+/* Write a scheme string STR to PORT from START inclusive to END
+   exclusive.  */
+void
+scm_lfwrite_substr (SCM str, size_t start, size_t end, SCM port)
+{
+  size_t i, size = scm_i_string_length (str);
+  scm_t_port *pt = SCM_PTAB_ENTRY (port);
+  scm_t_ptob_descriptor *ptob = &scm_ptobs[SCM_PTOBNUM (port)];
+  scm_t_wchar p;
+  char *buf;
+  size_t len;
+
+  if (pt->rw_active == SCM_PORT_READ)
+    scm_end_input (port);
+
+  if (end == (size_t) (-1))
+    end = size;
+  size = end - start;
+
+  /* Note that making a substring will likely take the
+     stringbuf_write_mutex.  So, one shouldn't use scm_lfwrite_substr
+     if the stringbuf write mutex may still be held elsewhere.  */
+  buf = scm_to_stringn (scm_c_substring (str, start, end), &len,
+                       pt->encoding, pt->ilseq_handler);
+  ptob->write (port, buf, len);
+  free (buf);
+
+  for (i = 0; i < size; i++)
+    {
+      p = scm_i_string_ref (str, i + start);
+      update_port_lf (p, port);
     }
-    else {
-      SCM_INCCOL(port);
+
+  if (pt->rw_random)
+    pt->rw_active = SCM_PORT_WRITE;
+}
+
+/* Write a scheme string STR to PORT.  */
+void
+scm_lfwrite_str (SCM str, SCM port)
+{
+  size_t i, size = scm_i_string_length (str);
+  scm_t_port *pt = SCM_PTAB_ENTRY (port);
+  scm_t_ptob_descriptor *ptob = &scm_ptobs[SCM_PTOBNUM (port)];
+  scm_t_wchar p;
+  char *buf;
+  size_t len;
+
+  if (pt->rw_active == SCM_PORT_READ)
+    scm_end_input (port);
+
+  buf = scm_to_stringn (str, &len,
+                       pt->encoding, pt->ilseq_handler);
+  ptob->write (port, buf, len);
+  free (buf);
+
+  for (i = 0; i < size; i++)
+    {
+      p = scm_i_string_ref (str, i);
+      update_port_lf (p, port);
     }
-  }
 
   if (pt->rw_random)
     pt->rw_active = SCM_PORT_WRITE;
@@ -1074,7 +1369,7 @@ scm_c_read (SCM port, void *buffer, size_t size)
      requested number of bytes.  (Note that a single scm_fill_input
      call does not guarantee to fill the whole of the port's read
      buffer.) */
-  if (pt->read_buf_size <= 1)
+  if (pt->read_buf_size <= 1 && pt->encoding == NULL)
     {
       /* The port that we are reading from is unbuffered - i.e. does
         not have its own persistent buffer - but we have a buffer,
@@ -1086,7 +1381,14 @@ scm_c_read (SCM port, void *buffer, size_t size)
         We need to make sure that the port's normal (1 byte) buffer
         is reinstated in case one of the scm_fill_input () calls
         throws an exception; we use the scm_dynwind_* API to achieve
-        that. */
+        that. 
+
+         A consequence of this optimization is that the fill_input
+         functions can't unget characters.  That'll push data to the
+         pushback buffer instead of this psb buffer.  */
+#if SCM_DEBUG == 1
+      unsigned char *pback = pt->putback_buf;
+#endif      
       psb.pt = pt;
       psb.buffer = buffer;
       psb.size = size;
@@ -1101,8 +1403,15 @@ scm_c_read (SCM port, void *buffer, size_t size)
          pt->read_buf_size -= (pt->read_end - pt->read_pos);
          pt->read_pos = pt->read_buf = pt->read_end;
        }
+#if SCM_DEBUG == 1
+      if (pback != pt->putback_buf 
+          || pt->read_buf - (unsigned char *) buffer < 0)
+        scm_misc_error (FUNC_NAME, 
+                        "scm_c_read must not call a fill function that pushes "
+                        "back characters onto an unbuffered port", SCM_EOL);
+#endif      
       n_read += pt->read_buf - (unsigned char *) buffer;
-
+      
       /* Reinstate the port's normal buffer. */
       scm_dynwind_end ();
     }
@@ -1160,10 +1469,11 @@ scm_c_write (SCM port, const void *ptr, size_t size)
 }
 #undef FUNC_NAME
 
-void 
+void
 scm_flush (SCM port)
 {
   long i = SCM_PTOBNUM (port);
+  assert (i >= 0);
   (scm_ptobs[i].flush) (port);
 }
 
@@ -1191,8 +1501,8 @@ scm_end_input (SCM port)
 
 
 void 
-scm_ungetc (int c, SCM port)
-#define FUNC_NAME "scm_ungetc"
+scm_unget_byte (int c, SCM port)
+#define FUNC_NAME "scm_unget_byte"
 {
   scm_t_port *pt = SCM_PTAB_ENTRY (port);
 
@@ -1232,8 +1542,8 @@ scm_ungetc (int c, SCM port)
       if (pt->putback_buf == NULL)
        {
          pt->putback_buf
-           = (unsigned char *) scm_gc_malloc (SCM_INITIAL_PUTBACK_BUF_SIZE,
-                                              "putback buffer");
+           = (unsigned char *) scm_gc_malloc_pointerless
+           (SCM_INITIAL_PUTBACK_BUF_SIZE, "putback buffer");
          pt->putback_buf_size = SCM_INITIAL_PUTBACK_BUF_SIZE;
        }
 
@@ -1251,6 +1561,47 @@ scm_ungetc (int c, SCM port)
 
   if (pt->rw_random)
     pt->rw_active = SCM_PORT_READ;
+}
+#undef FUNC_NAME
+
+void
+scm_ungetc (scm_t_wchar c, SCM port)
+#define FUNC_NAME "scm_ungetc"
+{
+  scm_t_port *pt = SCM_PTAB_ENTRY (port);
+  char *result;
+  char result_buf[10];
+  const char *encoding;
+  size_t len;
+  int i;
+
+  if (pt->encoding != NULL)
+    encoding = pt->encoding;
+  else
+    encoding = "ISO-8859-1";
+
+  len = sizeof (result_buf);
+  result = u32_conv_to_encoding (encoding,
+                                (enum iconv_ilseq_handler) pt->ilseq_handler,
+                                (uint32_t *) &c, 1, NULL,
+                                result_buf, &len);
+
+  if (SCM_UNLIKELY (result == NULL || len == 0))
+    {
+      SCM chr;
+
+      chr = scm_integer_to_char (scm_from_uint32 (c));
+      scm_encoding_error (FUNC_NAME, errno,
+                         "conversion to port encoding failed",
+                         "UTF-32", encoding,
+                         scm_string (scm_list_1 (chr)));
+    }
+
+  for (i = len - 1; i >= 0; i--)
+    scm_unget_byte (result[i], port);
+
+  if (SCM_UNLIKELY (result != result_buf))
+    free (result);
 
   if (c == '\n')
     {
@@ -1297,18 +1648,37 @@ SCM_DEFINE (scm_peek_char, "peek-char", 0, 1, 0,
            "to @code{read-char} would have hung.")
 #define FUNC_NAME s_scm_peek_char
 {
-  int c, column;
+  SCM result;
+  scm_t_wchar c;
+  char bytes[SCM_MBCHAR_BUF_SIZE];
+  long column, line;
+  size_t len;
+
   if (SCM_UNBNDP (port))
     port = scm_current_input_port ();
   else
     SCM_VALIDATE_OPINPORT (1, port);
-  column = SCM_COL(port);
-  c = scm_getc (port);
-  if (EOF == c)
-    return SCM_EOF_VAL;
-  scm_ungetc (c, port);
-  SCM_COL(port) = column;
-  return SCM_MAKE_CHAR (c);
+
+  column = SCM_COL (port);
+  line = SCM_LINUM (port);
+
+  c = get_codepoint (port, bytes, &len);
+  if (c == EOF)
+    result = SCM_EOF_VAL;
+  else
+    {
+      long i;
+
+      result = SCM_MAKE_CHAR (c);
+
+      for (i = len - 1; i >= 0; i--)
+       scm_unget_byte (bytes[i], port);
+
+      SCM_COL (port) = column;
+      SCM_LINUM (port) = line;
+    }
+
+  return result;
 }
 #undef FUNC_NAME
 
@@ -1343,13 +1713,17 @@ SCM_DEFINE (scm_unread_string, "unread-string", 2, 0, 0,
            "@var{port} is not supplied, the current-input-port is used.")
 #define FUNC_NAME s_scm_unread_string
 {
+  int n;
   SCM_VALIDATE_STRING (1, str);
   if (SCM_UNBNDP (port))
     port = scm_current_input_port ();
   else
     SCM_VALIDATE_OPINPORT (2, port);
 
-  scm_ungets (scm_i_string_chars (str), scm_i_string_length (str), port);
+  n = scm_i_string_length (str);
+
+  while (n--)
+    scm_ungetc (scm_i_string_ref (str, n), port);
   
   return str;
 }
@@ -1604,6 +1978,330 @@ SCM_DEFINE (scm_set_port_filename_x, "set-port-filename!", 2, 0, 0,
 }
 #undef FUNC_NAME
 
+/* A fluid specifying the default encoding for newly created ports.  If it is
+   a string, that is the encoding.  If it is #f, it is in the "native"
+   (Latin-1) encoding.  */
+SCM_VARIABLE (default_port_encoding_var, "%default-port-encoding");
+
+static int scm_port_encoding_init = 0;
+
+/* Return a C string representation of the current encoding.  */
+const char *
+scm_i_get_port_encoding (SCM port)
+{
+  SCM encoding;
+  
+  if (scm_is_false (port))
+    {
+      if (!scm_port_encoding_init)
+       return NULL;
+      else if (!scm_is_fluid (SCM_VARIABLE_REF (default_port_encoding_var)))
+       return NULL;
+      else
+       {
+         encoding = scm_fluid_ref (SCM_VARIABLE_REF (default_port_encoding_var));
+         if (!scm_is_string (encoding))
+           return NULL;
+         else
+           return scm_i_string_chars (encoding);
+       }
+    }
+  else
+    {
+      scm_t_port *pt;
+      pt = SCM_PTAB_ENTRY (port);
+      if (pt->encoding)
+       return pt->encoding;
+      else
+       return NULL;
+    }
+}
+
+/* Returns ENC if it is a recognized encoding.  If it isn't, it tries
+   to find an alias of ENC that is valid.  Otherwise, it returns
+   NULL.  */
+static const char *
+find_valid_encoding (const char *enc)
+{
+  int isvalid = 0;
+  const char str[] = " ";
+  scm_t_uint32 result_buf;
+  scm_t_uint32 *u32;
+  size_t u32len;
+
+  u32len = sizeof (result_buf) / sizeof (scm_t_uint32);
+  u32 = u32_conv_from_encoding (enc, iconveh_error, str, 1,
+                                NULL, &result_buf, &u32len);
+  isvalid = (u32 != NULL);
+
+  if (SCM_UNLIKELY (u32 != &result_buf))
+    free (u32);
+
+  if (isvalid)
+    return enc;
+
+  return NULL;
+}
+
+void
+scm_i_set_port_encoding_x (SCM port, const char *enc)
+{
+  const char *valid_enc;
+  scm_t_port *pt;
+
+  /* Null is shorthand for the native, Latin-1 encoding.  */
+  if (enc == NULL)
+    valid_enc = NULL;
+  else
+    {
+      valid_enc = find_valid_encoding (enc);
+      if (valid_enc == NULL)
+        {
+          SCM err;
+          err = scm_from_locale_string (enc);
+          scm_misc_error (NULL, "invalid or unknown character encoding ~s",
+                          scm_list_1 (err));
+        }
+    }
+
+  if (scm_is_false (port))
+    {
+      /* Set the default encoding for future ports.  */
+      if (!scm_port_encoding_init
+         || !scm_is_fluid (SCM_VARIABLE_REF (default_port_encoding_var)))
+       scm_misc_error (NULL, "tried to set port encoding fluid before it is initialized",
+                       SCM_EOL);
+
+      if (valid_enc == NULL 
+          || !strcmp (valid_enc, "ASCII")
+          || !strcmp (valid_enc, "ANSI_X3.4-1968")
+          || !strcmp (valid_enc, "ISO-8859-1"))
+        scm_fluid_set_x (SCM_VARIABLE_REF (default_port_encoding_var), SCM_BOOL_F);
+      else
+        scm_fluid_set_x (SCM_VARIABLE_REF (default_port_encoding_var), 
+                         scm_from_locale_string (valid_enc));
+    }
+  else
+    {
+      /* Set the character encoding for this port.  */
+      pt = SCM_PTAB_ENTRY (port);
+      if (valid_enc == NULL)
+        pt->encoding = NULL;
+      else
+        pt->encoding = scm_gc_strdup (valid_enc, "port");
+    }
+}
+
+SCM_DEFINE (scm_port_encoding, "port-encoding", 1, 0, 0,
+           (SCM port),
+           "Returns, as a string, the character encoding that @var{port}\n"
+           "uses to interpret its input and output.\n")
+#define FUNC_NAME s_scm_port_encoding
+{
+  scm_t_port *pt;
+  const char *enc;
+
+  SCM_VALIDATE_PORT (1, port);
+
+  pt = SCM_PTAB_ENTRY (port);
+  enc = scm_i_get_port_encoding (port);
+  if (enc)
+    return scm_from_locale_string (pt->encoding);
+  else
+    return SCM_BOOL_F;
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_set_port_encoding_x, "set-port-encoding!", 2, 0, 0,
+           (SCM port, SCM enc),
+           "Sets the character encoding that will be used to interpret all\n"
+           "port I/O.  New ports are created with the encoding\n"
+           "appropriate for the current locale if @code{setlocale} has \n"
+           "been called or ISO-8859-1 otherwise\n"
+           "and this procedure can be used to modify that encoding.\n")
+#define FUNC_NAME s_scm_set_port_encoding_x
+{
+  char *enc_str;
+  const char *valid_enc_str;
+
+  SCM_VALIDATE_PORT (1, port);
+  SCM_VALIDATE_STRING (2, enc);
+
+  enc_str = scm_to_locale_string (enc);
+  valid_enc_str = find_valid_encoding (enc_str);
+  if (valid_enc_str == NULL)
+    {
+      free (enc_str);
+      scm_misc_error (FUNC_NAME, "invalid or unknown character encoding ~s",
+                     scm_list_1 (enc));
+    }
+  else
+    {
+      scm_i_set_port_encoding_x (port, valid_enc_str);
+      free (enc_str);
+    }
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+
+/* This determines how conversions handle unconvertible characters.  */
+SCM_GLOBAL_VARIABLE (scm_conversion_strategy, "%port-conversion-strategy");
+static int scm_conversion_strategy_init = 0;
+
+scm_t_string_failed_conversion_handler
+scm_i_get_conversion_strategy (SCM port)
+{
+  SCM encoding;
+  
+  if (scm_is_false (port))
+    {
+      if (!scm_conversion_strategy_init
+         || !scm_is_fluid (SCM_VARIABLE_REF (scm_conversion_strategy)))
+       return SCM_FAILED_CONVERSION_QUESTION_MARK;
+      else
+       {
+         encoding = scm_fluid_ref (SCM_VARIABLE_REF (scm_conversion_strategy));
+         if (scm_is_false (encoding))
+           return SCM_FAILED_CONVERSION_QUESTION_MARK;
+         else 
+           return (scm_t_string_failed_conversion_handler) scm_to_int (encoding);
+       }
+    }
+  else
+    {
+      scm_t_port *pt;
+      pt = SCM_PTAB_ENTRY (port);
+       return pt->ilseq_handler;
+    }
+      
+}
+
+void
+scm_i_set_conversion_strategy_x (SCM port, 
+                                scm_t_string_failed_conversion_handler handler)
+{
+  SCM strategy;
+  scm_t_port *pt;
+  
+  strategy = scm_from_int ((int) handler);
+  
+  if (scm_is_false (port))
+    {
+      /* Set the default encoding for future ports.  */
+      if (!scm_conversion_strategy
+         || !scm_is_fluid (SCM_VARIABLE_REF (scm_conversion_strategy)))
+       scm_misc_error (NULL, "tried to set conversion strategy fluid before it is initialized",
+                       SCM_EOL);
+      scm_fluid_set_x (SCM_VARIABLE_REF (scm_conversion_strategy), strategy);
+    }
+  else
+    {
+      /* Set the character encoding for this port.  */
+      pt = SCM_PTAB_ENTRY (port);
+      pt->ilseq_handler = handler;
+    }
+}
+
+SCM_DEFINE (scm_port_conversion_strategy, "port-conversion-strategy",
+           1, 0, 0, (SCM port),
+           "Returns the behavior of the port when handling a character that\n"
+           "is not representable in the port's current encoding.\n"
+           "It returns the symbol @code{error} if unrepresentable characters\n"
+           "should cause exceptions, @code{substitute} if the port should\n"
+           "try to replace unrepresentable characters with question marks or\n"
+           "approximate characters, or @code{escape} if unrepresentable\n"
+           "characters should be converted to string escapes.\n"
+           "\n"
+           "If @var{port} is @code{#f}, then the current default behavior\n"
+           "will be returned.  New ports will have this default behavior\n"
+           "when they are created.\n")
+#define FUNC_NAME s_scm_port_conversion_strategy
+{
+  scm_t_string_failed_conversion_handler h;
+
+  SCM_VALIDATE_OPPORT (1, port);
+
+  if (!scm_is_false (port))
+    {
+      SCM_VALIDATE_OPPORT (1, port);
+    }
+
+  h = scm_i_get_conversion_strategy (port);
+  if (h == SCM_FAILED_CONVERSION_ERROR)
+    return scm_from_locale_symbol ("error");
+  else if (h == SCM_FAILED_CONVERSION_QUESTION_MARK)
+    return scm_from_locale_symbol ("substitute");
+  else if (h == SCM_FAILED_CONVERSION_ESCAPE_SEQUENCE)
+    return scm_from_locale_symbol ("escape");
+  else
+    abort ();
+
+  /* Never gets here. */
+  return SCM_UNDEFINED;
+}
+#undef FUNC_NAME
+
+SCM_DEFINE (scm_set_port_conversion_strategy_x, "set-port-conversion-strategy!",
+           2, 0, 0, 
+           (SCM port, SCM sym),
+           "Sets the behavior of the interpreter when outputting a character\n"
+           "that is not representable in the port's current encoding.\n"
+           "@var{sym} can be either @code{'error}, @code{'substitute}, or\n"
+           "@code{'escape}.  If it is @code{'error}, an error will be thrown\n"
+           "when an unconvertible character is encountered.  If it is\n"
+           "@code{'substitute}, then unconvertible characters will \n"
+           "be replaced with approximate characters, or with question marks\n"
+           "if no approximately correct character is available.\n"
+           "If it is @code{'escape},\n"
+           "it will appear as a hex escape when output.\n"
+           "\n"
+           "If @var{port} is an open port, the conversion error behavior\n"
+           "is set for that port.  If it is @code{#f}, it is set as the\n"
+           "default behavior for any future ports that get created in\n"
+           "this thread.\n")
+#define FUNC_NAME s_scm_set_port_conversion_strategy_x
+{
+  SCM err;
+  SCM qm;
+  SCM esc;
+
+  if (!scm_is_false (port))
+    {
+      SCM_VALIDATE_OPPORT (1, port);
+    }
+
+  err = scm_from_locale_symbol ("error");
+  if (scm_is_true (scm_eqv_p (sym, err)))
+    {
+      scm_i_set_conversion_strategy_x (port, SCM_FAILED_CONVERSION_ERROR);
+      return SCM_UNSPECIFIED;
+    }
+
+  qm = scm_from_locale_symbol ("substitute");
+  if (scm_is_true (scm_eqv_p (sym, qm)))
+    {
+      scm_i_set_conversion_strategy_x (port, 
+                                       SCM_FAILED_CONVERSION_QUESTION_MARK);
+      return SCM_UNSPECIFIED;
+    }
+
+  esc = scm_from_locale_symbol ("escape");
+  if (scm_is_true (scm_eqv_p (sym, esc)))
+    {
+      scm_i_set_conversion_strategy_x (port,
+                                       SCM_FAILED_CONVERSION_ESCAPE_SEQUENCE);
+      return SCM_UNSPECIFIED;
+    }
+
+  SCM_MISC_ERROR ("unknown conversion behavior ~s", scm_list_1 (sym));
+
+  return SCM_UNSPECIFIED;
+}
+#undef FUNC_NAME
+
+
+
 void
 scm_print_port_mode (SCM exp, SCM port)
 {
@@ -1634,13 +2332,6 @@ scm_port_print (SCM exp, SCM port, scm_print_state *pstate SCM_UNUSED)
   return 1;
 }
 
-void
-scm_ports_prehistory ()
-{
-  scm_numptob = 0;
-  scm_ptobs = (scm_t_ptob_descriptor *) scm_malloc (sizeof (scm_t_ptob_descriptor));
-}
-
 \f
 
 /* Void ports.   */
@@ -1708,14 +2399,25 @@ scm_init_ports ()
   scm_tc16_void_port = scm_make_port_type ("void", fill_input_void_port, 
                                           write_void_port);
 
-  cur_inport_fluid = scm_permanent_object (scm_make_fluid ());
-  cur_outport_fluid = scm_permanent_object (scm_make_fluid ());
-  cur_errport_fluid = scm_permanent_object (scm_make_fluid ());
-  cur_loadport_fluid = scm_permanent_object (scm_make_fluid ());
+  cur_inport_fluid = scm_make_fluid ();
+  cur_outport_fluid = scm_make_fluid ();
+  cur_errport_fluid = scm_make_fluid ();
+  cur_loadport_fluid = scm_make_fluid ();
+
+  scm_i_port_weak_hash = scm_make_weak_key_hash_table (SCM_I_MAKINUM(31));
 
-  scm_i_port_weak_hash = scm_permanent_object (scm_make_weak_key_hash_table (SCM_I_MAKINUM(31)));
-  
 #include "libguile/ports.x"
+
+  /* Use Latin-1 as the default port encoding.  */
+  SCM_VARIABLE_SET (default_port_encoding_var, scm_make_fluid ());
+  scm_fluid_set_x (SCM_VARIABLE_REF (default_port_encoding_var), SCM_BOOL_F);
+  scm_port_encoding_init = 1;
+
+  SCM_VARIABLE_SET (scm_conversion_strategy, scm_make_fluid ());
+  scm_fluid_set_x (SCM_VARIABLE_REF (scm_conversion_strategy), 
+                  scm_from_int ((int) SCM_FAILED_CONVERSION_QUESTION_MARK));
+  scm_conversion_strategy_init = 1;
+  
 }
 
 /*