code:
/* A stack holds a frame chain
 * Copyright (C) 1996,1997,2000,2001, 2006, 2007, 2008, 2009, 2010 Free Software Foundation
 *
 * This library 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.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "libguile/_scm.h"
#include "libguile/control.h"
#include "libguile/eval.h"
#include "libguile/debug.h"
#include "libguile/continuations.h"
#include "libguile/struct.h"
#include "libguile/macros.h"
#include "libguile/procprop.h"
#include "libguile/modules.h"
#include "libguile/root.h"
#include "libguile/strings.h"
#include "libguile/vm.h" /* to capture vm stacks */
#include "libguile/frames.h" /* vm frames */

#include "libguile/validate.h"
#include "libguile/stacks.h"
#include "libguile/private-options.h"

static SCM scm_sys_stacks;

/* {Stacks}
 *
 * The stack is represented as a struct that holds a frame. The frame itself is
 * linked to the next frame, or #f.
 *
 * Stacks
 *   Constructor
 *     make-stack
 *   Selectors
 *     stack-id
 *     stack-ref
 *   Inspector
 *     stack-length
 */

/* Count number of debug info frames on a stack, beginning with FRAME.
 */
static long
stack_depth (SCM frame)
{
  long n = 0;
  /* count frames, skipping boot frames */
  for (; scm_is_true (frame); frame = scm_frame_previous (frame))
    ++n;
  return n;
}

/* Narrow STACK by cutting away stackframes (mutatingly).
 *
 * Inner frames (most recent) are cut by advancing the frames pointer.
 * Outer frames are cut by decreasing the recorded length.
 *
 * Cut maximally INNER inner frames and OUTER outer frames using
 * the keys INNER_KEY and OUTER_KEY.
 *
 * Frames are cut away starting at the end points and moving towards
 * the center of the stack.  The key is normally compared to the
 * operator in application frames.  Frames up to and including the key
 * are cut.
 *
 * If INNER_KEY is #t a different scheme is used for inner frames:
 *
 * Frames up to but excluding the first source frame originating from
 * a user module are cut, except for possible application frames
 * between the user frame and the last system frame previously
 * encountered.
 */

static SCM
find_prompt (SCM key)
{
  SCM winds;
  for (winds = scm_i_dynwinds (); scm_is_pair (winds); winds = scm_cdr (winds))
    {
      SCM elt = scm_car (winds);
      if (SCM_PROMPT_P (elt) && SCM_PROMPT_TAG (elt) == key)
        return elt;
    }
  scm_misc_error ("make-stack", "Prompt tag not found while narrowing stack",
                  scm_list_1 (key));
  return SCM_BOOL_F; /* not reached */
}

static void
narrow_stack (SCM stack, long inner, SCM inner_key, long outer, SCM outer_key)
{
  unsigned long int len;
  SCM frame;

  len = SCM_STACK_LENGTH (stack);
  frame = SCM_STACK_FRAME (stack);

  /* Cut inner part. */
  if (scm_is_true (scm_procedure_p (inner_key)))
    {
      /* Cut until the given procedure is seen. */
      for (; inner && len ; --inner)
        {
          SCM proc = scm_frame_procedure (frame);
          len--;
          frame = scm_frame_previous (frame);
          if (scm_is_eq (proc, inner_key))
            break;
        }
    }
  else if (scm_is_symbol (inner_key))
    {
      /* Cut until the given prompt tag is seen. FIXME, assumes prompt tags are
         symbols. */
      SCM prompt = find_prompt (inner_key);
      for (; len; len--, frame = scm_frame_previous (frame))
        if (SCM_PROMPT_REGISTERS (prompt)->fp
            == SCM_VM_FRAME_FP (frame) - SCM_VM_FRAME_OFFSET (frame))
          break;
    }
  else
    {
      /* Cut specified number of frames. */
      for (; inner && len; --inner)
        {
          len--;
          frame = scm_frame_previous (frame);
        }
    }

  SCM_SET_STACK_LENGTH (stack, len);
  SCM_SET_STACK_FRAME (stack, frame);

  /* Cut outer part. */
  if (scm_is_true (scm_procedure_p (outer_key)))
    {
      /* Cut until the given procedure is seen. */
      for (; outer && len ; --outer)
        {
          frame = scm_stack_ref (stack, scm_from_long (len - 1));
          len--;
          if (scm_is_eq (scm_frame_procedure (frame), outer_key))
            break;
        }
    }
  else if (scm_is_symbol (outer_key))
    {
      /* Cut until the given prompt tag is seen. FIXME, assumes prompt tags are
         symbols. */
      SCM prompt = find_prompt (outer_key);
      while (len)
        {
          frame = scm_stack_ref (stack, scm_from_long (len - 1));
          len--;
          if (SCM_PROMPT_REGISTERS (prompt)->fp
              == SCM_VM_FRAME_FP (frame) - SCM_VM_FRAME_OFFSET (frame))
            break;
        }
    }
  else
    {
      /* Cut specified number of frames. */
      for (; outer && len ; --outer)
        {
          frame = scm_stack_ref (stack, scm_from_long (len - 1));
          len--;
        }
    }

  SCM_SET_STACK_LENGTH (stack, len);
}

/* Stacks
 */

SCM scm_stack_type;

SCM_DEFINE (scm_stack_p, "stack?", 1, 0, 0,
            (SCM obj),
	    "Return @code{#t} if @var{obj} is a calling stack.")
#define FUNC_NAME s_scm_stack_p
{
  return scm_from_bool(SCM_STACKP (obj));
}
#undef FUNC_NAME

SCM_DEFINE (scm_make_stack, "make-stack", 1, 0, 1,
            (SCM obj, SCM args),
	    "Create a new stack. If @var{obj} is @code{#t}, the current\n"
	    "evaluation stack is used for creating the stack frames,\n"
	    "otherwise the frames are taken from @var{obj} (which must be\n"
	    "a continuation or a frame object).\n"
            "\n"
	    "@var{args} should be a list containing any combination of\n"
	    "integer, procedure, prompt tag and @code{#t} values.\n"
            "\n"
	    "These values specify various ways of cutting away uninteresting\n"
	    "stack frames from the top and bottom of the stack that\n"
	    "@code{make-stack} returns.  They come in pairs like this:\n"
	    "@code{(@var{inner_cut_1} @var{outer_cut_1} @var{inner_cut_2}\n"
	    "@var{outer_cut_2} @dots{})}.\n"
            "\n"
	    "Each @var{inner_cut_N} can be @code{#t}, an integer, a prompt\n"
            "tag, or a procedure.  @code{#t} means to cut away all frames up\n"
            "to but excluding the first user module frame.  An integer means\n"
            "to cut away exactly that number of frames.  A prompt tag means\n"
            "to cut away all frames that are inside a prompt with the given\n"
            "tag. A procedure means to cut away all frames up to but\n"
            "excluding the application frame whose procedure matches the\n"
            "specified one.\n"
            "\n"
	    "Each @var{outer_cut_N} can be an integer, a prompt tag, or a\n"
            "procedure.  An integer means to cut away that number of frames.\n"
            "A prompt tag means to cut away all frames that are outside a\n"
            "prompt with the given tag. A procedure means to cut away\n"
            "frames down to but excluding the application frame whose\n"
            "procedure matches the specified one.\n"
            "\n"
	    "If the @var{outer_cut_N} of the last pair is missing, it is\n"
	    "taken as 0.")
#define FUNC_NAME s_scm_make_stack
{
  long n;
  int maxp;
  SCM frame;
  SCM stack;
  SCM inner_cut, outer_cut;

  /* Extract a pointer to the innermost frame of whatever object
     scm_make_stack was given.  */
  if (scm_is_eq (obj, SCM_BOOL_T))
    {
      SCM cont;
      struct scm_vm_cont *c;

      cont = scm_i_vm_capture_continuation (scm_the_vm ());
      c = SCM_VM_CONT_DATA (cont);

      frame = scm_c_make_frame (cont, c->fp + c->reloc,
                                c->sp + c->reloc, c->ra,
                                c->reloc);
    }
  else if (SCM_VM_FRAME_P (obj))
    frame = obj;
  else if (SCM_CONTINUATIONP (obj))
    /* FIXME: Narrowing to prompt tags should narrow with respect to the prompts
       that were in place when the continuation was captured. */
    frame = scm_i_continuation_to_frame (obj);
  else
    {
      SCM_WRONG_TYPE_ARG (SCM_ARG1, obj);
      /* not reached */
    }

  /* FIXME: is this even possible? */
  if (scm_is_true (frame)
      && SCM_PROGRAM_IS_BOOT (scm_frame_procedure (frame)))
    frame = scm_frame_previous (frame);

  if (scm_is_false (frame))
    return SCM_BOOL_F;

  /* Count number of frames.  Also get stack id tag and check whether
     there are more stackframes than we want to record
     (SCM_BACKTRACE_MAXDEPTH). */
  maxp = 0;
  n = stack_depth (frame);

  /* Make the stack object. */
  stack = scm_make_struct (scm_stack_type, SCM_INUM0, SCM_EOL);
  SCM_SET_STACK_LENGTH (stack, n);
  SCM_SET_STACK_ID (stack, scm_stack_id (obj));
  SCM_SET_STACK_FRAME (stack, frame);

  /* Narrow the stack according to the arguments given to scm_make_stack. */
  SCM_VALIDATE_REST_ARGUMENT (args);
  while (n > 0 && !scm_is_null (args))
    {
      inner_cut = SCM_CAR (args);
      args = SCM_CDR (args);
      if (scm_is_null (args))
	{
	  outer_cut = SCM_INUM0;
	}
      else
	{
	  outer_cut = SCM_CAR (args);
	  args = SCM_CDR (args);
	}

      narrow_stack (stack,
		    scm_is_integer (inner_cut) ? scm_to_int (inner_cut) : n,
		    scm_is_integer (inner_cut) ? SCM_BOOL_T : inner_cut,
		    scm_is_integer (outer_cut) ? scm_to_int (outer_cut) : n,
		    scm_is_integer (outer_cut) ? SCM_BOOL_T : outer_cut);

      n = SCM_STACK_LENGTH (stack);
    }

  if (n > 0)
    return stack;
  else
    return SCM_BOOL_F;
}
#undef FUNC_NAME

SCM_DEFINE (scm_stack_id, "stack-id", 1, 0, 0,
            (SCM stack),
	    "Return the identifier given to @var{stack} by @code{start-stack}.")
#define FUNC_NAME s_scm_stack_id
{
  if (scm_is_eq (stack, SCM_BOOL_T)
      /* FIXME: frame case assumes frame still live on the stack, and no
         intervening start-stack. Hmm... */
      || SCM_VM_FRAME_P (stack))
    {
      /* Fetch most recent start-stack tag. */
      SCM stacks = scm_fluid_ref (scm_sys_stacks);
      return scm_is_pair (stacks) ? scm_caar (stacks) : SCM_BOOL_F;
    }
  else if (SCM_CONTINUATIONP (stack))
    /* FIXME: implement me */
    return SCM_BOOL_F;
  else
    {
      SCM_WRONG_TYPE_ARG (SCM_ARG1, stack);
      /* not reached */
    }
}
#undef FUNC_NAME

SCM_DEFINE (scm_stack_ref, "stack-ref", 2, 0, 0,
            (SCM stack, SCM index),
	    "Return the @var{index}'th frame from @var{stack}.")
#define FUNC_NAME s_scm_stack_ref
{
  unsigned long int c_index;
  SCM frame;

  SCM_VALIDATE_STACK (1, stack);
  c_index = scm_to_unsigned_integer (index, 0, SCM_STACK_LENGTH(stack)-1);
  frame = SCM_STACK_FRAME (stack);
  while (c_index--)
    frame = scm_frame_previous (frame);
  return frame;
}
#undef FUNC_NAME

SCM_DEFINE (scm_stack_length, "stack-length", 1, 0, 0,
	    (SCM stack),
	    "Return the length of @var{stack}.")
#define FUNC_NAME s_scm_stack_length
{
  SCM_VALIDATE_STACK (1, stack);
  return scm_from_long (SCM_STACK_LENGTH (stack));
}
#undef FUNC_NAME

void
scm_init_stacks ()
{
  scm_sys_stacks = scm_make_fluid ();
  scm_c_define ("%stacks", scm_sys_stacks);

  scm_stack_type = scm_make_vtable (scm_from_locale_string (SCM_STACK_LAYOUT),
                                    SCM_UNDEFINED);
  scm_set_struct_vtable_name_x (scm_stack_type,
				scm_from_locale_symbol ("stack"));
#include "libguile/stacks.x"
}

/*
  Local Variables:
  c-file-style: "gnu"
  End:
*/


code:
/* classes: h_files */

#ifndef SCM_STACKS_H
#define SCM_STACKS_H

/* Copyright (C) 1995,1996,2000,2001, 2004, 2006, 2008 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
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "libguile/__scm.h"
#include "libguile/frames.h"

/* {Frames and stacks}
 */

SCM_API SCM scm_stack_type;

#define SCM_STACK_LAYOUT                        \
  "pw" /* len */                                \
  "pw" /* id */                                 \
  "pw" /* frame */

#define SCM_STACKP(obj) (SCM_STRUCTP (obj) && scm_is_eq (SCM_STRUCT_VTABLE (obj), scm_stack_type))
#define SCM_STACK_LENGTH(obj) (scm_to_long (SCM_STRUCT_SLOT_REF (obj,0)))
#define SCM_SET_STACK_LENGTH(obj,f) (SCM_STRUCT_SLOT_SET (obj,0,scm_from_long (f)))
#define SCM_STACK_ID(obj) (SCM_STRUCT_SLOT_REF (obj,1))
#define SCM_SET_STACK_ID(obj,f) (SCM_STRUCT_SLOT_SET (obj,1,f))
#define SCM_STACK_FRAME(obj) (SCM_STRUCT_SLOT_REF (obj,2))
#define SCM_SET_STACK_FRAME(obj,f) (SCM_STRUCT_SLOT_SET (obj,2,f))

#define SCM_FRAMEP(obj) (SCM_VM_FRAME_P (obj))

SCM_API SCM scm_stack_p (SCM obj);
SCM_API SCM scm_make_stack (SCM obj, SCM args);
SCM_API SCM scm_stack_id (SCM stack);
SCM_API SCM scm_stack_ref (SCM stack, SCM i);
SCM_API SCM scm_stack_length (SCM stack);

SCM_INTERNAL void scm_init_stacks (void);

#endif  /* SCM_STACKS_H */

/*
  Local Variables:
  c-file-style: "gnu"
  End:
*/

license: mit


code:
/* Copyright (C) 1995,1996,1997,1998,1999,2000,2001, 2003, 2004, 2005, 2006, 2007, 2008, 2009 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
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

/* _POSIX_C_SOURCE is not defined always, because it causes problems on some
   systems, notably

       - FreeBSD loses all BSD and XOPEN defines.
       - glibc loses some things like CLK_TCK.
       - On MINGW it conflicts with the pthread headers.

   But on HP-UX _POSIX_C_SOURCE is needed, as noted, for gmtime_r.

   Perhaps a configure test could figure out what _POSIX_C_SOURCE gives and
   what it takes away, and decide from that whether to use it, instead of
   hard coding __hpux.  */

#ifndef _REENTRANT
# define _REENTRANT   /* ask solaris for gmtime_r prototype */
#endif
#ifdef __hpux
#define _POSIX_C_SOURCE 199506L  /* for gmtime_r prototype */
#endif

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <strftime.h>
#include <unistr.h>

#include "libguile/_scm.h"
#include "libguile/async.h"
#include "libguile/feature.h"
#include "libguile/strings.h"
#include "libguile/vectors.h"
#include "libguile/dynwind.h"
#include "libguile/strings.h"

#include "libguile/validate.h"
#include "libguile/stime.h"

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

# ifdef HAVE_SYS_TYPES_H
#  include <sys/types.h>
# endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_SYS_TIMES_H
# include <sys/times.h>
#endif

#ifdef HAVE_SYS_TIMEB_H
# include <sys/timeb.h>
#endif

#ifndef tzname /* For SGI.  */
extern char *tzname[]; /* RS6000 and others reject char **tzname.  */
#endif
#if defined (__MINGW32__)
# define tzname _tzname
#endif

#if ! HAVE_DECL_STRPTIME
extern char *strptime ();
#endif

#ifdef __STDC__
# define timet time_t
#else
# define timet long
#endif

#ifdef HAVE_TIMES
static
timet mytime()
{
  struct tms time_buffer;
  times(&time_buffer);
  return time_buffer.tms_utime + time_buffer.tms_stime;
}
#else
# ifdef LACK_CLOCK
#    define mytime() ((time((timet*)0) - scm_your_base) * SCM_TIME_UNITS_PER_SECOND)
# else
#  define mytime clock
# endif
#endif

#ifdef HAVE_FTIME
struct timeb scm_your_base = {0};
#else
timet scm_your_base = 0;
#endif

SCM_DEFINE (scm_get_internal_real_time, "get-internal-real-time", 0, 0, 0,
           (),
	    "Return the number of time units since the interpreter was\n"
	    "started.")
#define FUNC_NAME s_scm_get_internal_real_time
{
#ifdef HAVE_FTIME
  struct timeb time_buffer;

  SCM tmp;
  ftime (&time_buffer);
  time_buffer.time -= scm_your_base.time;
  tmp = scm_from_long (time_buffer.millitm - scm_your_base.millitm);
  tmp = scm_sum (tmp,
		 scm_product (scm_from_int (1000),
			      scm_from_int (time_buffer.time)));
  return scm_quotient (scm_product (tmp,
				    scm_from_int (SCM_TIME_UNITS_PER_SECOND)),
		       scm_from_int (1000));
#else
  return scm_from_long ((time((timet*)0) - scm_your_base)
			* (int)SCM_TIME_UNITS_PER_SECOND);
#endif /* HAVE_FTIME */
}
#undef FUNC_NAME

#ifdef HAVE_TIMES
SCM_DEFINE (scm_times, "times", 0, 0, 0,
            (void),
	    "Return an object with information about real and processor\n"
	    "time.  The following procedures accept such an object as an\n"
	    "argument and return a selected component:\n"
	    "\n"
	    "@table @code\n"
	    "@item tms:clock\n"
	    "The current real time, expressed as time units relative to an\n"
	    "arbitrary base.\n"
	    "@item tms:utime\n"
	    "The CPU time units used by the calling process.\n"
	    "@item tms:stime\n"
	    "The CPU time units used by the system on behalf of the calling\n"
	    "process.\n"
	    "@item tms:cutime\n"
	    "The CPU time units used by terminated child processes of the\n"
	    "calling process, whose status has been collected (e.g., using\n"
	    "@code{waitpid}).\n"
	    "@item tms:cstime\n"
	    "Similarly, the CPU times units used by the system on behalf of\n"
	    "terminated child processes.\n"
	    "@end table")
#define FUNC_NAME s_scm_times
{
  struct tms t;
  clock_t rv;

  SCM result = scm_c_make_vector (5, SCM_UNDEFINED);
  rv = times (&t);
  if (rv == -1)
    SCM_SYSERROR;
  SCM_SIMPLE_VECTOR_SET (result, 0, scm_from_long (rv));
  SCM_SIMPLE_VECTOR_SET (result, 1, scm_from_long (t.tms_utime));
  SCM_SIMPLE_VECTOR_SET (result, 2, scm_from_long (t.tms_stime));
  SCM_SIMPLE_VECTOR_SET (result ,3, scm_from_long (t.tms_cutime));
  SCM_SIMPLE_VECTOR_SET (result, 4, scm_from_long (t.tms_cstime));
  return result;
}
#undef FUNC_NAME
#endif /* HAVE_TIMES */

static long scm_my_base = 0;

long
scm_c_get_internal_run_time ()
{
  return mytime () - scm_my_base;
}

SCM_DEFINE (scm_get_internal_run_time, "get-internal-run-time", 0, 0, 0,
           (void),
	    "Return the number of time units of processor time used by the\n"
	    "interpreter.  Both @emph{system} and @emph{user} time are\n"
	    "included but subprocesses are not.")
#define FUNC_NAME s_scm_get_internal_run_time
{
  return scm_from_long (scm_c_get_internal_run_time ());
}
#undef FUNC_NAME

/* For reference, note that current-time and gettimeofday both should be
   protected against setzone/restorezone changes in another thread, since on
   DOS the system time is normally kept as local time, which means TZ
   affects the return from current-time and gettimeofday.  Not sure if DJGPP
   etc actually has concurrent multi-threading, but it seems prudent not to
   make assumptions about this.  */

SCM_DEFINE (scm_current_time, "current-time", 0, 0, 0,
           (void),
	    "Return the number of seconds since 1970-01-01 00:00:00 UTC,\n"
	    "excluding leap seconds.")
#define FUNC_NAME s_scm_current_time
{
  timet timv;

  SCM_CRITICAL_SECTION_START;
  timv = time (NULL);
  SCM_CRITICAL_SECTION_END;
  if (timv == -1)
    SCM_MISC_ERROR ("current time not available", SCM_EOL);
  return scm_from_long (timv);
}
#undef FUNC_NAME

SCM_DEFINE (scm_gettimeofday, "gettimeofday", 0, 0, 0,
            (void),
	    "Return a pair containing the number of seconds and microseconds\n"
	    "since 1970-01-01 00:00:00 UTC, excluding leap seconds.  Note:\n"
	    "whether true microsecond resolution is available depends on the\n"
	    "operating system.")
#define FUNC_NAME s_scm_gettimeofday
{
#ifdef HAVE_GETTIMEOFDAY
  struct timeval time;
  int ret, err;

  SCM_CRITICAL_SECTION_START;
  ret = gettimeofday (&time, NULL);
  err = errno;
  SCM_CRITICAL_SECTION_END;
  if (ret == -1)
    {
      errno = err;
      SCM_SYSERROR;
    }
  return scm_cons (scm_from_long (time.tv_sec),
		   scm_from_long (time.tv_usec));
#else
# ifdef HAVE_FTIME
  struct timeb time;

  ftime(&time);
  return scm_cons (scm_from_long (time.time),
		   scm_from_int (time.millitm * 1000));
# else
  timet timv;
  int err;

  SCM_CRITICAL_SECTION_START;
  timv = time (NULL);
  err = errno;
  SCM_CRITICAL_SECTION_END;
  if (timv == -1)
    {
      errno = err;
      SCM_SYSERROR;
    }
  return scm_cons (scm_from_long (timv), scm_from_int (0));
# endif
#endif
}
#undef FUNC_NAME

static SCM
filltime (struct tm *bd_time, int zoff, const char *zname)
{
  SCM result = scm_c_make_vector (11, SCM_UNDEFINED);

  SCM_SIMPLE_VECTOR_SET (result,0, scm_from_int (bd_time->tm_sec));
  SCM_SIMPLE_VECTOR_SET (result,1, scm_from_int (bd_time->tm_min));
  SCM_SIMPLE_VECTOR_SET (result,2, scm_from_int (bd_time->tm_hour));
  SCM_SIMPLE_VECTOR_SET (result,3, scm_from_int (bd_time->tm_mday));
  SCM_SIMPLE_VECTOR_SET (result,4, scm_from_int (bd_time->tm_mon));
  SCM_SIMPLE_VECTOR_SET (result,5, scm_from_int (bd_time->tm_year));
  SCM_SIMPLE_VECTOR_SET (result,6, scm_from_int (bd_time->tm_wday));
  SCM_SIMPLE_VECTOR_SET (result,7, scm_from_int (bd_time->tm_yday));
  SCM_SIMPLE_VECTOR_SET (result,8, scm_from_int (bd_time->tm_isdst));
  SCM_SIMPLE_VECTOR_SET (result,9, scm_from_int (zoff));
  SCM_SIMPLE_VECTOR_SET (result,10, (zname
				     ? scm_from_locale_string (zname)
				     : SCM_BOOL_F));
  return result;
}

static char tzvar[3] = "TZ";

/* if zone is set, create a temporary environment with only a TZ
   string.  other threads or interrupt handlers shouldn't be allowed
   to run until the corresponding restorezone is called.  hence the use
   of a static variable for tmpenv is no big deal.  */
static char **
setzone (SCM zone, int pos, const char *subr)
{
  char **oldenv = 0;

  if (!SCM_UNBNDP (zone))
    {
      static char *tmpenv[2];
      char *buf;
      size_t zone_len;

      zone_len = scm_to_locale_stringbuf (zone, NULL, 0);
      buf = scm_malloc (zone_len + sizeof (tzvar) + 1);
      strcpy (buf, tzvar);
      buf[sizeof(tzvar)-1] = '=';
      scm_to_locale_stringbuf (zone, buf+sizeof(tzvar), zone_len);
      buf[sizeof(tzvar)+zone_len] = '\0';
      oldenv = environ;
      tmpenv[0] = buf;
      tmpenv[1] = 0;
      environ = tmpenv;
    }
  return oldenv;
}

static void
restorezone (SCM zone, char **oldenv, const char *subr SCM_UNUSED)
{
  if (!SCM_UNBNDP (zone))
    {
      free (environ[0]);
      environ = oldenv;
#ifdef HAVE_TZSET
      /* for the possible benefit of user code linked with libguile.  */
      tzset();
#endif
    }
}

SCM_DEFINE (scm_localtime, "localtime", 1, 1, 0,
            (SCM time, SCM zone),
	    "Return an object representing the broken down components of\n"
	    "@var{time}, an integer like the one returned by\n"
	    "@code{current-time}.  The time zone for the calculation is\n"
	    "optionally specified by @var{zone} (a string), otherwise the\n"
	    "@code{TZ} environment variable or the system default is used.")
#define FUNC_NAME s_scm_localtime
{
  timet itime;
  struct tm *ltptr, lt, *utc;
  SCM result;
  int zoff;
  char *zname = 0;
  char **oldenv;
  int err;

  itime = SCM_NUM2LONG (1, time);

  /* deferring interupts is essential since a) setzone may install a temporary
     environment b) localtime uses a static buffer.  */
  SCM_CRITICAL_SECTION_START;
  oldenv = setzone (zone, SCM_ARG2, FUNC_NAME);
#ifdef LOCALTIME_CACHE
  tzset ();
#endif
  /* POSIX says localtime sets errno, but C99 doesn't say that.
     Give a sensible default value in case localtime doesn't set it.  */
  errno = EINVAL;
  ltptr = localtime (&itime);
  err = errno;
  if (ltptr)
    {
      const char *ptr;

      /* copy zone name before calling gmtime or restoring zone.  */
#if defined (HAVE_TM_ZONE)
      ptr = ltptr->tm_zone;
#elif defined (HAVE_TZNAME)
      ptr = tzname[ (ltptr->tm_isdst == 1) ? 1 : 0 ];
#else
      ptr = "";
#endif
      zname = scm_malloc (strlen (ptr) + 1);
      strcpy (zname, ptr);
    }
  /* the struct is copied in case localtime and gmtime share a buffer.  */
  if (ltptr)
    lt = *ltptr;
  /* POSIX says gmtime sets errno, but C99 doesn't say that.
     Give a sensible default value in case gmtime doesn't set it.  */
  errno = EINVAL;
  utc = gmtime (&itime);
  if (utc == NULL)
    err = errno;
  restorezone (zone, oldenv, FUNC_NAME);
  /* delayed until zone has been restored.  */
  errno = err;
  if (utc == NULL || ltptr == NULL)
    SCM_SYSERROR;

  /* calculate timezone offset in seconds west of UTC.  */
  zoff = (utc->tm_hour - lt.tm_hour) * 3600 + (utc->tm_min - lt.tm_min) * 60
    + utc->tm_sec - lt.tm_sec;
  if (utc->tm_year < lt.tm_year)
    zoff -= 24 * 60 * 60;
  else if (utc->tm_year > lt.tm_year)
    zoff += 24 * 60 * 60;
  else if (utc->tm_yday < lt.tm_yday)
    zoff -= 24 * 60 * 60;
  else if (utc->tm_yday > lt.tm_yday)
    zoff += 24 * 60 * 60;

  result = filltime (&lt, zoff, zname);
  SCM_CRITICAL_SECTION_END;

  free (zname);
  return result;
}
#undef FUNC_NAME

/* tm_zone is normally a pointer, not an array within struct tm, so we might
   have to worry about the lifespan of what it points to.  The posix specs
   don't seem to say anything about this, let's assume here that tm_zone
   will be a constant and therefore no protection or anything is needed
   until we copy it in filltime().  */

SCM_DEFINE (scm_gmtime, "gmtime", 1, 0, 0,
            (SCM time),
	    "Return an object representing the broken down components of\n"
	    "@var{time}, an integer like the one returned by\n"
	    "@code{current-time}.  The values are calculated for UTC.")
#define FUNC_NAME s_scm_gmtime
{
  timet itime;
  struct tm bd_buf, *bd_time;
  const char *zname;

  itime = SCM_NUM2LONG (1, time);

  /* POSIX says gmtime sets errno, but C99 doesn't say that.
     Give a sensible default value in case gmtime doesn't set it.  */
  errno = EINVAL;

#if HAVE_GMTIME_R
  bd_time = gmtime_r (&itime, &bd_buf);
#else
  SCM_CRITICAL_SECTION_START;
  bd_time = gmtime (&itime);
  if (bd_time != NULL)
    bd_buf = *bd_time;
  SCM_CRITICAL_SECTION_END;
#endif
  if (bd_time == NULL)
    SCM_SYSERROR;

#if HAVE_STRUCT_TM_TM_ZONE
  zname = bd_buf.tm_zone;
#else
  zname = "GMT";
#endif
  return filltime (&bd_buf, 0, zname);
}
#undef FUNC_NAME

/* copy time components from a Scheme object to a struct tm.  */
static void
bdtime2c (SCM sbd_time, struct tm *lt, int pos, const char *subr)
{
  SCM_ASSERT (scm_is_simple_vector (sbd_time)
	      && SCM_SIMPLE_VECTOR_LENGTH (sbd_time) == 11,
	      sbd_time, pos, subr);

  lt->tm_sec = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 0));
  lt->tm_min = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 1));
  lt->tm_hour = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 2));
  lt->tm_mday = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 3));
  lt->tm_mon = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 4));
  lt->tm_year = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 5));
  lt->tm_wday = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 6));
  lt->tm_yday = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 7));
  lt->tm_isdst = scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 8));
#if HAVE_STRUCT_TM_TM_GMTOFF
  lt->tm_gmtoff = - scm_to_int (SCM_SIMPLE_VECTOR_REF (sbd_time, 9));
#endif
#ifdef HAVE_TM_ZONE
  if (scm_is_false (SCM_SIMPLE_VECTOR_REF (sbd_time, 10)))
    lt->tm_zone = NULL;
  else
    lt->tm_zone  = scm_to_locale_string (SCM_SIMPLE_VECTOR_REF (sbd_time, 10));
#endif
}

SCM_DEFINE (scm_mktime, "mktime", 1, 1, 0,
            (SCM sbd_time, SCM zone),
	    "@var{bd-time} is an object representing broken down time and @code{zone}\n"
	    "is an optional time zone specifier (otherwise the TZ environment variable\n"
	    "or the system default is used).\n\n"
	    "Returns a pair: the car is a corresponding\n"
	    "integer time value like that returned\n"
	    "by @code{current-time}; the cdr is a broken down time object, similar to\n"
	    "as @var{bd-time} but with normalized values.")
#define FUNC_NAME s_scm_mktime
{
  timet itime;
  struct tm lt, *utc;
  SCM result;
  int zoff;
  char *zname = 0;
  char **oldenv;
  int err;

  scm_dynwind_begin (0);

  bdtime2c (sbd_time, &lt, SCM_ARG1, FUNC_NAME);
#if HAVE_STRUCT_TM_TM_ZONE
  scm_dynwind_free ((char *)lt.tm_zone);
#endif

  scm_dynwind_critical_section (SCM_BOOL_F);

  oldenv = setzone (zone, SCM_ARG2, FUNC_NAME);
#ifdef LOCALTIME_CACHE
  tzset ();
#endif
  itime = mktime (&lt);
  /* POSIX doesn't say mktime sets errno, and on glibc 2.3.2 for instance it
     doesn't.  Force a sensible value for our error message.  */
  err = EINVAL;

  if (itime != -1)
    {
      const char *ptr;

      /* copy zone name before calling gmtime or restoring the zone.  */
#if defined (HAVE_TM_ZONE)
      ptr = lt.tm_zone;
#elif defined (HAVE_TZNAME)
      ptr = tzname[ (lt.tm_isdst == 1) ? 1 : 0 ];
#else
      ptr = "";
#endif
      zname = scm_malloc (strlen (ptr) + 1);
      strcpy (zname, ptr);
    }

  /* get timezone offset in seconds west of UTC.  */
  /* POSIX says gmtime sets errno, but C99 doesn't say that.
     Give a sensible default value in case gmtime doesn't set it.  */
  errno = EINVAL;
  utc = gmtime (&itime);
  if (utc == NULL)
    err = errno;

  restorezone (zone, oldenv, FUNC_NAME);
  /* delayed until zone has been restored.  */
  errno = err;
  if (utc == NULL || itime == -1)
    SCM_SYSERROR;

  zoff = (utc->tm_hour - lt.tm_hour) * 3600 + (utc->tm_min - lt.tm_min) * 60
    + utc->tm_sec - lt.tm_sec;
  if (utc->tm_year < lt.tm_year)
    zoff -= 24 * 60 * 60;
  else if (utc->tm_year > lt.tm_year)
    zoff += 24 * 60 * 60;
  else if (utc->tm_yday < lt.tm_yday)
    zoff -= 24 * 60 * 60;
  else if (utc->tm_yday > lt.tm_yday)
    zoff += 24 * 60 * 60;

  result = scm_cons (scm_from_long (itime),
		     filltime (&lt, zoff, zname));
  free (zname);

  scm_dynwind_end ();
  return result;
}
#undef FUNC_NAME

#ifdef HAVE_TZSET
SCM_DEFINE (scm_tzset, "tzset", 0, 0, 0,
            (void),
	    "Initialize the timezone from the TZ environment variable\n"
	    "or the system default.  It's not usually necessary to call this procedure\n"
	    "since it's done automatically by other procedures that depend on the\n"
	    "timezone.")
#define FUNC_NAME s_scm_tzset
{
  tzset();
  return SCM_UNSPECIFIED;
}
#undef FUNC_NAME
#endif /* HAVE_TZSET */

SCM_DEFINE (scm_strftime, "strftime", 2, 0, 0,
            (SCM format, SCM stime),
	    "Return a string which is broken-down time structure @var{stime}\n"
	    "formatted according to the given @var{format} string.\n"
	    "\n"
	    "@var{format} contains field specifications introduced by a\n"
	    "@samp{%} character.  See @ref{Formatting Calendar Time,,, libc,\n"
	    "The GNU C Library Reference Manual}, or @samp{man 3 strftime},\n"
	    "for the available formatting.\n"
	    "\n"
	    "@lisp\n"
	    "(strftime \"%c\" (localtime (current-time)))\n"
	    "@result{} \"Mon Mar 11 20:17:43 2002\"\n"
	    "@end lisp\n"
	    "\n"
	    "If @code{setlocale} has been called (@pxref{Locales}), month\n"
	    "and day names are from the current locale and in the locale\n"
	    "character set.")
#define FUNC_NAME s_scm_strftime
{
  struct tm t;

  scm_t_uint8 *tbuf;
  int size = 50;
  scm_t_uint8 *fmt;
  scm_t_uint8 *myfmt;
  int len;
  SCM result;

  SCM_VALIDATE_STRING (1, format);
  bdtime2c (stime, &t, SCM_ARG2, FUNC_NAME);

  /* Convert string to UTF-8 so that non-ASCII characters in the
     format are passed through unchanged.  */
  fmt = scm_i_to_utf8_string (format);
  len = strlen ((const char *) fmt);

  /* Ugly hack: strftime can return 0 if its buffer is too small,
     but some valid time strings (e.g. "%p") can sometimes produce
     a zero-byte output string!  Workaround is to prepend a junk
     character to the format string, so that valid returns are always
     nonzero. */
  myfmt = scm_malloc (len+2);
  *myfmt = (scm_t_uint8) 'x';
  strncpy ((char *) myfmt + 1, (const char *) fmt, len);
  myfmt[len + 1] = 0;
  scm_remember_upto_here_1 (format);
  free (fmt);

  tbuf = scm_malloc (size);
  {
#if !defined (HAVE_TM_ZONE)
    /* it seems the only way to tell non-GNU versions of strftime what
       zone to use (for the %Z format) is to set TZ in the
       environment.  interrupts and thread switching must be deferred
       until TZ is restored.  */
    char **oldenv = NULL;
    SCM zone_spec = SCM_SIMPLE_VECTOR_REF (stime, 10);
    int have_zone = 0;

    if (scm_is_true (zone_spec) && scm_c_string_length (zone_spec) > 0)
      {
	/* it's not required that the TZ setting be correct, just that
	   it has the right name.  so try something like TZ=EST0.
	   using only TZ=EST would be simpler but it doesn't work on
	   some OSs, e.g., Solaris.  */
	SCM zone =
	  scm_string_append (scm_list_2 (zone_spec,
					 scm_from_locale_string ("0")));

	have_zone = 1;
	SCM_CRITICAL_SECTION_START;
	oldenv = setzone (zone, SCM_ARG2, FUNC_NAME);
      }
#endif

#ifdef LOCALTIME_CACHE
    tzset ();
#endif

    /* Use `nstrftime ()' from Gnulib, which supports all GNU extensions
       supported by glibc.  */
    while ((len = nstrftime ((char *) tbuf, size,
			     (const char *) myfmt, &t, 0, 0)) == 0)
      {
	free (tbuf);
	size *= 2;
	tbuf = scm_malloc (size);
      }

#if !defined (HAVE_TM_ZONE)
    if (have_zone)
      {
	restorezone (zone_spec, oldenv, FUNC_NAME);
	SCM_CRITICAL_SECTION_END;
      }
#endif
    }

  result = scm_i_from_utf8_string ((const scm_t_uint8 *) tbuf + 1);
  free (tbuf);
  free (myfmt);
#if HAVE_STRUCT_TM_TM_ZONE
  free ((char *) t.tm_zone);
#endif
  return result;
}
#undef FUNC_NAME

#ifdef HAVE_STRPTIME
SCM_DEFINE (scm_strptime, "strptime", 2, 0, 0,
            (SCM format, SCM string),
	    "Performs the reverse action to @code{strftime}, parsing\n"
	    "@var{string} according to the specification supplied in\n"
	    "@var{template}.  The interpretation of month and day names is\n"
	    "dependent on the current locale.  The value returned is a pair.\n"
	    "The car has an object with time components\n"
	    "in the form returned by @code{localtime} or @code{gmtime},\n"
	    "but the time zone components\n"
	    "are not usefully set.\n"
	    "The cdr reports the number of characters from @var{string}\n"
	    "which were used for the conversion.")
#define FUNC_NAME s_scm_strptime
{
  struct tm t;
  scm_t_uint8 *fmt, *str, *rest;
  size_t used_len;
  long zoff;

  SCM_VALIDATE_STRING (1, format);
  SCM_VALIDATE_STRING (2, string);

  /* Convert strings to UTF-8 so that non-ASCII characters are passed
     through unchanged.  */
  fmt = scm_i_to_utf8_string (format);
  str = scm_i_to_utf8_string (string);

  /* initialize the struct tm */
#define tm_init(field) t.field = 0
  tm_init (tm_sec);
  tm_init (tm_min);
  tm_init (tm_hour);
  tm_init (tm_mday);
  tm_init (tm_mon);
  tm_init (tm_year);
  tm_init (tm_wday);
  tm_init (tm_yday);
#if HAVE_STRUCT_TM_TM_GMTOFF
  tm_init (tm_gmtoff);
#endif
#undef tm_init

  /* GNU glibc strptime() "%s" is affected by the current timezone, since it
     reads a UTC time_t value and converts with localtime_r() to set the tm
     fields, hence the use of SCM_CRITICAL_SECTION_START.  */
  t.tm_isdst = -1;
  SCM_CRITICAL_SECTION_START;
  rest = (scm_t_uint8 *) strptime ((const char *) str,
                                   (const char *) fmt, &t);
  SCM_CRITICAL_SECTION_END;
  if (rest == NULL)
    {
      /* POSIX doesn't say strptime sets errno, and on glibc 2.3.2 for
         instance it doesn't.  Force a sensible value for our error
         message.  */
      errno = EINVAL;
      scm_remember_upto_here_2 (format, string);
      free (str);
      free (fmt);
      SCM_SYSERROR;
    }

  /* tm_gmtoff is set by GNU glibc strptime "%s", so capture it when
     available */
#if HAVE_STRUCT_TM_TM_GMTOFF
  zoff = - t.tm_gmtoff;  /* seconds west, not east */
#else
  zoff = 0;
#endif

  /* Compute the number of UTF-8 characters.  */
  used_len = u8_strnlen (str, rest-str);
  scm_remember_upto_here_2 (format, string);
  free (str);
  free (fmt);

  return scm_cons (filltime (&t, zoff, NULL),
		   scm_from_signed_integer (used_len));
}
#undef FUNC_NAME
#endif /* HAVE_STRPTIME */

void
scm_init_stime()
{
  scm_c_define ("internal-time-units-per-second",
		scm_from_long (SCM_TIME_UNITS_PER_SECOND));

#ifdef HAVE_FTIME
  if (!scm_your_base.time) ftime(&scm_your_base);
#else
  if (!scm_your_base) time(&scm_your_base);
#endif

  if (!scm_my_base) scm_my_base = mytime();

  scm_add_feature ("current-time");
#include "libguile/stime.x"
}

/*
  Local Variables:
  c-file-style: "gnu"
  End:
*/


code:
/* classes: h_files */

#ifndef SCM_STIME_H
#define SCM_STIME_H

/* Copyright (C) 1995,1996,1997,1998,2000, 2003, 2006, 2008 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
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#include "libguile/__scm.h"

#include <unistd.h>  /* for sysconf */

/* This should be figured out by autoconf.

   sysconf(_SC_CLK_TCK) is best, since it's the actual running kernel, not
   some compile-time CLK_TCK.  On glibc 2.3.2 CLK_TCK (when defined) is in
   fact sysconf(_SC_CLK_TCK) anyway.

   CLK_TCK is obsolete in POSIX.  In glibc 2.3.2 it's defined by default,
   but if you define _GNU_SOURCE or _POSIX_C_SOURCE to get other features
   then it goes away.  */

#if ! defined(SCM_TIME_UNITS_PER_SECOND) && defined(_SC_CLK_TCK)
#  define SCM_TIME_UNITS_PER_SECOND ((int) sysconf (_SC_CLK_TCK))
#endif
#if ! defined(SCM_TIME_UNITS_PER_SECOND) && defined(CLK_TCK)
#  define SCM_TIME_UNITS_PER_SECOND ((int) CLK_TCK)
#endif
#if ! defined(SCM_TIME_UNITS_PER_SECOND) && defined(CLOCKS_PER_SEC)
#  define SCM_TIME_UNITS_PER_SECOND ((int) CLOCKS_PER_SEC)
#endif
#if ! defined(SCM_TIME_UNITS_PER_SECOND)
#  define SCM_TIME_UNITS_PER_SECOND 60
#endif

SCM_API long scm_c_get_internal_run_time (void);
SCM_API SCM scm_get_internal_real_time (void);
SCM_API SCM scm_get_internal_run_time (void);
SCM_API SCM scm_current_time (void);
SCM_API SCM scm_gettimeofday (void);
SCM_API SCM scm_localtime (SCM time, SCM zone);
SCM_API SCM scm_gmtime (SCM time);
SCM_API SCM scm_mktime (SCM sbd_time, SCM zone);
SCM_API SCM scm_tzset (void);
SCM_API SCM scm_times (void);
SCM_API SCM scm_strftime (SCM format, SCM stime);
SCM_API SCM scm_strptime (SCM format, SCM string);
SCM_INTERNAL void scm_init_stime (void);

#endif  /* SCM_STIME_H */

/*
  Local Variables:
  c-file-style: "gnu"
  End:
*/


code:
/* Turning errno values into English error messages.
   Copyright (C) 1985, 86, 87, 88, 93, 94, 95, 2000, 2001, 2006 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
   as published by the Free Software Foundation; either version 3 of
   the License, or (at your option) any later version.

   This library 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 this library; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   02110-1301 USA
*/

char *
strerror (int errnum)
{
  extern char *sys_errlist[];
  extern int sys_nerr;

  if (errnum >= 0 && errnum < sys_nerr)
    return sys_errlist[errnum];
  return (char *) "Unknown error";
}

/*
  Local Variables:
  c-file-style: "gnu"
  End:
*/


code:
/* Copyright (C) 1995,1996,1998,2000,2001, 2004, 2006, 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
 * as published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This library 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 this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <uninorm.h>
#include <unistr.h>
#include <uniconv.h>

#include "striconveh.h"

#include "libguile/_scm.h"
#include "libguile/chars.h"
#include "libguile/root.h"
#include "libguile/strings.h"
#include "libguile/error.h"
#include "libguile/generalized-vectors.h"
#include "libguile/deprecation.h"
#include "libguile/validate.h"
#include "libguile/private-options.h"

/* {Strings}
 */

/* Stringbufs
 *
 * XXX - keeping an accurate refcount during GC seems to be quite
 * tricky, so we just keep score of whether a stringbuf might be
 * shared, not whether it definitely is.
 *
 * The scheme I (mvo) tried to keep an accurate reference count would
 * recount all strings that point to a stringbuf during the mark-phase
 * of the GC.  This was done since one cannot access the stringbuf of
 * a string when that string is freed (in order to decrease the
 * reference count).  The memory of the stringbuf might have been
 * reused already for something completely different.
 *
 * This recounted worked for a small number of threads beating on
 * cow-strings, but it failed randomly with more than 10 threads, say.
 * I couldn't figure out what went wrong, so I used the conservative
 * approach implemented below.
 *
 * There are 2 storage strategies for stringbufs: 8-bit and wide.  8-bit
 * strings are ISO-8859-1-encoded strings; wide strings are 32-bit (UCS-4)
 * strings.
 */

/* The size in words of the stringbuf header (type tag + size).  */
#define STRINGBUF_HEADER_SIZE   2U

#define STRINGBUF_HEADER_BYTES  (STRINGBUF_HEADER_SIZE * sizeof (SCM))

#define STRINGBUF_F_SHARED      SCM_I_STRINGBUF_F_SHARED
#define STRINGBUF_F_WIDE        SCM_I_STRINGBUF_F_WIDE

#define STRINGBUF_TAG           scm_tc7_stringbuf
#define STRINGBUF_SHARED(buf)   (SCM_CELL_WORD_0(buf) & STRINGBUF_F_SHARED)
#define STRINGBUF_WIDE(buf)     (SCM_CELL_WORD_0(buf) & STRINGBUF_F_WIDE)

#define STRINGBUF_CONTENTS(buf) ((void *)				\
                                 SCM_CELL_OBJECT_LOC (buf,		\
						      STRINGBUF_HEADER_SIZE))
#define STRINGBUF_CHARS(buf)    ((unsigned char *) STRINGBUF_CONTENTS (buf))
#define STRINGBUF_WIDE_CHARS(buf) ((scm_t_wchar *) STRINGBUF_CONTENTS (buf))

#define STRINGBUF_LENGTH(buf)   (SCM_CELL_WORD_1 (buf))

#define SET_STRINGBUF_SHARED(buf)					\
  do									\
    {									\
      /* Don't modify BUF if it's already marked as shared since it might be \
	 a read-only, statically allocated stringbuf.  */		\
      if (SCM_LIKELY (!STRINGBUF_SHARED (buf)))				\
	SCM_SET_CELL_WORD_0 ((buf), SCM_CELL_WORD_0 (buf) | STRINGBUF_F_SHARED); \
    }									\
  while (0)

#ifdef SCM_STRING_LENGTH_HISTOGRAM
static size_t lenhist[1001];
#endif

/* Make a stringbuf with space for LEN 8-bit Latin-1-encoded
   characters. */
static SCM
make_stringbuf (size_t len)
{
  /* XXX - for the benefit of SCM_STRING_CHARS, SCM_SYMBOL_CHARS and
     scm_i_symbol_chars, all stringbufs are null-terminated.  Once
     SCM_STRING_CHARS and SCM_SYMBOL_CHARS are removed and the code
     has been changed for scm_i_symbol_chars, this null-termination
     can be dropped.
  */

  SCM buf;

#ifdef SCM_STRING_LENGTH_HISTOGRAM
  if (len < 1000)
    lenhist[len]++;
  else
    lenhist[1000]++;
#endif

  buf = PTR2SCM (scm_gc_malloc_pointerless (STRINGBUF_HEADER_BYTES + len + 1,
					    "string"));

  SCM_SET_CELL_TYPE (buf, STRINGBUF_TAG);
  SCM_SET_CELL_WORD_1 (buf, (scm_t_bits) len);

  STRINGBUF_CHARS (buf)[len] = 0;

  return buf;
}

/* Make a stringbuf with space for LEN 32-bit UCS-4-encoded
   characters.  */
static SCM
make_wide_stringbuf (size_t len)
{
  SCM buf;
  size_t raw_len;

#ifdef SCM_STRING_LENGTH_HISTOGRAM
  if (len < 1000)
    lenhist[len]++;
  else
    lenhist[1000]++;
#endif

  raw_len = (len + 1) * sizeof (scm_t_wchar);
  buf = PTR2SCM (scm_gc_malloc_pointerless (STRINGBUF_HEADER_BYTES + raw_len,
					    "string"));

  SCM_SET_CELL_TYPE (buf, STRINGBUF_TAG | STRINGBUF_F_WIDE);
  SCM_SET_CELL_WORD_1 (buf, (scm_t_bits) len);

  STRINGBUF_WIDE_CHARS (buf)[len] = 0;

  return buf;
}

/* Return a UCS-4-encoded stringbuf containing the (possibly Latin-1-encoded)
   characters from BUF.  */
static SCM
wide_stringbuf (SCM buf)
{
  SCM new_buf;

  if (STRINGBUF_WIDE (buf))
    new_buf = buf;
  else
    {
      size_t i, len;
      scm_t_wchar *mem;

      len = STRINGBUF_LENGTH (buf);

      new_buf = make_wide_stringbuf (len);

      mem = STRINGBUF_WIDE_CHARS (new_buf);
      for (i = 0; i < len; i++)
	mem[i] = (scm_t_wchar) STRINGBUF_CHARS (buf)[i];
      mem[len] = 0;
    }

  return new_buf;
}

/* Return a Latin-1-encoded stringbuf containing the (possibly UCS-4-encoded)
   characters from BUF, if possible.  */
static SCM
narrow_stringbuf (SCM buf)
{
  SCM new_buf;

  if (!STRINGBUF_WIDE (buf))
    new_buf = buf;
  else
    {
      size_t i, len;
      scm_t_wchar *wmem;
      unsigned char *mem;

      len = STRINGBUF_LENGTH (buf);
      wmem = STRINGBUF_WIDE_CHARS (buf);

      for (i = 0; i < len; i++)
	if (wmem[i] > 0xFF)
	  /* BUF cannot be narrowed.  */
	  return buf;

      new_buf = make_stringbuf (len);

      mem = STRINGBUF_CHARS (new_buf);
      for (i = 0; i < len; i++)
	mem[i] = (unsigned char) wmem[i];
      mem[len] = 0;
    }

  return new_buf;
}

scm_i_pthread_mutex_t stringbuf_write_mutex = SCM_I_PTHREAD_MUTEX_INITIALIZER;

/* Copy-on-write strings.
 */

#define STRING_TAG            scm_tc7_string

#define STRING_STRINGBUF(str) (SCM_CELL_OBJECT_1(str))
#define STRING_START(str)     ((size_t)SCM_CELL_WORD_2(str))
#define STRING_LENGTH(str)    ((size_t)SCM_CELL_WORD_3(str))

#define SET_STRING_STRINGBUF(str,buf) (SCM_SET_CELL_OBJECT_1(str,buf))
#define SET_STRING_START(str,start) (SCM_SET_CELL_WORD_2(str,start))

#define IS_STRING(str)        (SCM_NIMP(str) && SCM_TYP7(str) == STRING_TAG)

/* Read-only strings.
 */

#define RO_STRING_TAG         scm_tc7_ro_string
#define IS_RO_STRING(str)     (SCM_CELL_TYPE(str)==RO_STRING_TAG)

/* Mutation-sharing substrings
 */

#define SH_STRING_TAG       (scm_tc7_string + 0x100)

#define SH_STRING_STRING(sh) (SCM_CELL_OBJECT_1(sh))
/* START and LENGTH as for STRINGs. */

#define IS_SH_STRING(str)   (SCM_CELL_TYPE(str)==SH_STRING_TAG)

SCM scm_nullstr;

/* Create a scheme string with space for LEN 8-bit Latin-1-encoded
   characters.  CHARSP, if not NULL, will be set to location of the
   char array.  */
SCM
scm_i_make_string (size_t len, char **charsp)
{
  SCM buf = make_stringbuf (len);
  SCM res;
  if (charsp)
    *charsp = (char *) STRINGBUF_CHARS (buf);
  res = scm_double_cell (STRING_TAG, SCM_UNPACK(buf),
			 (scm_t_bits)0, (scm_t_bits) len);
  return res;
}

/* Create a scheme string with space for LEN 32-bit UCS-4-encoded
   characters.  CHARSP, if not NULL, will be set to location of the
   character array.  */
SCM
scm_i_make_wide_string (size_t len, scm_t_wchar **charsp)
{
  SCM buf = make_wide_stringbuf (len);
  SCM res;
  if (charsp)
    *charsp = STRINGBUF_WIDE_CHARS (buf);
  res = scm_double_cell (STRING_TAG, SCM_UNPACK (buf),
                         (scm_t_bits) 0, (scm_t_bits) len);
  return res;
}

static void
validate_substring_args (SCM str, size_t start, size_t end)
{
  if (!IS_STRING (str))
    scm_wrong_type_arg_msg (NULL, 0, str, "string");
  if (start > STRING_LENGTH (str))
    scm_out_of_range (NULL, scm_from_size_t (start));
  if (end > STRING_LENGTH (str) || end < start)
    scm_out_of_range (NULL, scm_from_size_t (end));
}

static inline void
get_str_buf_start (SCM *str, SCM *buf, size_t *start)
{
  *start = STRING_START (*str);
  if (IS_SH_STRING (*str))
    {
      *str = SH_STRING_STRING (*str);
      *start += STRING_START (*str);
    }
  *buf = STRING_STRINGBUF (*str);
}

SCM
scm_i_substring (SCM str, size_t start, size_t end)
{
  SCM buf;
  size_t str_start;
  get_str_buf_start (&str, &buf, &str_start);
  scm_i_pthread_mutex_lock (&stringbuf_write_mutex);
  SET_STRINGBUF_SHARED (buf);
  scm_i_pthread_mutex_unlock (&stringbuf_write_mutex);
  return scm_double_cell (STRING_TAG, SCM_UNPACK(buf),
			  (scm_t_bits)str_start + start,
			  (scm_t_bits) end - start);
}

SCM
scm_i_substring_read_only (SCM str, size_t start, size_t end)
{
  SCM buf;
  size_t str_start;
  get_str_buf_start (&str, &buf, &str_start);
  scm_i_pthread_mutex_lock (&stringbuf_write_mutex);
  SET_STRINGBUF_SHARED (buf);
  scm_i_pthread_mutex_unlock (&stringbuf_write_mutex);
  return scm_double_cell (RO_STRING_TAG, SCM_UNPACK(buf),
			  (scm_t_bits)str_start + start,
			  (scm_t_bits) end - start);
}

SCM
scm_i_substring_copy (SCM str, size_t start, size_t end)
{
  size_t len = end - start;
  SCM buf, my_buf;
  size_t str_start;
  get_str_buf_start (&str, &buf, &str_start);
  if (scm_i_is_narrow_string (str))
    {
      my_buf = make_stringbuf (len);
      memcpy (STRINGBUF_CHARS (my_buf),
              STRINGBUF_CHARS (buf) + str_start + start, len);
    }
  else
    {
      my_buf = make_wide_stringbuf (len);
      u32_cpy ((scm_t_uint32 *) STRINGBUF_WIDE_CHARS (my_buf),
               (scm_t_uint32 *) (STRINGBUF_WIDE_CHARS (buf) + str_start
                                 + start), len);
      /* Even though this string is wide, the substring may be narrow.
         Consider adding code to narrow the string.  */
    }
  scm_remember_upto_here_1 (buf);
  return scm_double_cell (STRING_TAG, SCM_UNPACK (my_buf),
                          (scm_t_bits) 0, (scm_t_bits) len);
}

SCM
scm_i_substring_shared (SCM str, size_t start, size_t end)
{
  if (start == 0 && end == STRING_LENGTH (str))
    return str;
  else
    {
      size_t len = end - start;
      if (IS_SH_STRING (str))
	{
	  start += STRING_START (str);
	  str = SH_STRING_STRING (str);
	}
      return scm_double_cell (SH_STRING_TAG, SCM_UNPACK(str),
			      (scm_t_bits)start, (scm_t_bits) len);
    }
}

SCM
scm_c_substring (SCM str, size_t start, size_t end)
{
  validate_substring_args (str, start, end);
  return scm_i_substring (str, start, end);
}

SCM
scm_c_substring_read_only (SCM str, size_t start, size_t end)
{
  validate_substring_args (str, start, end);
  return scm_i_substring_read_only (str, start, end);
}

SCM
scm_c_substring_copy (SCM str, size_t start, size_t end)
{
  validate_substring_args (str, start, end);
  return scm_i_substring_copy (str, start, end);
}

SCM
scm_c_substring_shared (SCM str, size_t start, size_t end)
{
  validate_substring_args (str, start, end);
  return scm_i_substring_shared (str, start, end);
}

/* Internal accessors
 */

/* Returns the number of characters in STR.  This may be different
   than the memory size of the string storage.  */
size_t
scm_i_string_length (SCM str)
{
  return STRING_LENGTH (str);
}

/* True if the string is 'narrow', meaning it has a 8-bit Latin-1
   encoding.  False if it is 'wide', having a 32-bit UCS-4
   encoding.  */
int
scm_i_is_narrow_string (SCM str)
{
  return !STRINGBUF_WIDE (STRING_STRINGBUF (str));
}

/* Try to coerce a string to be narrow.  It if is narrow already, do
   nothing.  If it is wide, shrink it to narrow if none of its
   characters are above 0xFF.  Return true if the string is narrow or
   was made to be narrow.  */
int
scm_i_try_narrow_string (SCM str)
{
  SET_STRING_STRINGBUF (str, narrow_stringbuf (STRING_STRINGBUF (str)));

  return scm_i_is_narrow_string (str);
}

/* Return a pointer to the raw data of the string, which can be either Latin-1
   or UCS-4 encoded data, depending on `scm_i_is_narrow_string (STR)'.  */
const void *
scm_i_string_data (SCM str)
{
  SCM buf;
  size_t start;
  const char *data;

  get_str_buf_start (&str, &buf, &start);

  data = STRINGBUF_CONTENTS (buf);
  data += start * (scm_i_is_narrow_string (str) ? 1 : 4);

  return data;
}

/* Returns a pointer to the 8-bit Latin-1 encoded character array of
   STR.  */
const char *
scm_i_string_chars (SCM str)
{
  SCM buf;
  size_t start;
  get_str_buf_start (&str, &buf, &start);
  if (scm_i_is_narrow_string (str))
    return (const char *) STRINGBUF_CHARS (buf) + start;
  else
    scm_misc_error (NULL, "Invalid read access of chars of wide string: ~s",
                    scm_list_1 (str));
  return NULL;
}

/* Returns a pointer to the 32-bit UCS-4 encoded character array of
   STR.  */
const scm_t_wchar *
scm_i_string_wide_chars (SCM str)
{
  SCM buf;
  size_t start;

  get_str_buf_start (&str, &buf, &start);
  if (!scm_i_is_narrow_string (str))
    return (const scm_t_wchar *) STRINGBUF_WIDE_CHARS (buf) + start;
  else
    scm_misc_error (NULL, "Invalid read access of chars of narrow string: ~s",
                    scm_list_1 (str));
}

/* If the buffer in ORIG_STR is shared, copy ORIG_STR's characters to
   a new string buffer, so that it can be modified without modifying
   other strings.  Also, lock the string mutex.  Later, one must call
   scm_i_string_stop_writing to unlock the mutex.  */
SCM
scm_i_string_start_writing (SCM orig_str)
{
  SCM buf, str = orig_str;
  size_t start;

  get_str_buf_start (&str, &buf, &start);
  if (IS_RO_STRING (str))
    scm_misc_error (NULL, "string is read-only: ~s", scm_list_1 (orig_str));

  scm_i_pthread_mutex_lock (&stringbuf_write_mutex);
  if (STRINGBUF_SHARED (buf))
    {
      /* Clone the stringbuf.  */
      size_t len = STRING_LENGTH (str);
      SCM new_buf;

      scm_i_pthread_mutex_unlock (&stringbuf_write_mutex);

      if (scm_i_is_narrow_string (str))
        {
          new_buf = make_stringbuf (len);
          memcpy (STRINGBUF_CHARS (new_buf),
                  STRINGBUF_CHARS (buf) + STRING_START (str), len);

        }
      else
        {
          new_buf = make_wide_stringbuf (len);
          u32_cpy ((scm_t_uint32 *) STRINGBUF_WIDE_CHARS (new_buf),
                   (scm_t_uint32 *) (STRINGBUF_WIDE_CHARS (buf)
                                     + STRING_START (str)), len);
        }

      SET_STRING_STRINGBUF (str, new_buf);
      start -= STRING_START (str);

      /* FIXME: The following operations are not atomic, so other threads
	 looking at STR may see an inconsistent state.  Nevertheless it can't
	 hurt much since (i) accessing STR while it is being mutated can't
	 yield a crash, and (ii) concurrent accesses to STR should be
	 protected by a mutex at the application level.  The latter may not
	 apply when STR != ORIG_STR, though.  */
      SET_STRING_START (str, 0);
      SET_STRING_STRINGBUF (str, new_buf);

      buf = new_buf;

      scm_i_pthread_mutex_lock (&stringbuf_write_mutex);
    }
  return orig_str;
}

/* Return a pointer to the 8-bit Latin-1 chars of a string.  */
char *
scm_i_string_writable_chars (SCM str)
{
  SCM buf;
  size_t start;

  get_str_buf_start (&str, &buf, &start);
  if (scm_i_is_narrow_string (str))
    return (char *) STRINGBUF_CHARS (buf) + start;
  else
    scm_misc_error (NULL, "Invalid write access of chars of wide string: ~s",
                    scm_list_1 (str));
  return NULL;
}

/* Return a pointer to the UCS-4 codepoints of a string.  */
static scm_t_wchar *
scm_i_string_writable_wide_chars (SCM str)
{
  SCM buf;
  size_t start;

  get_str_buf_start (&str, &buf, &start);
  if (!scm_i_is_narrow_string (str))
    return STRINGBUF_WIDE_CHARS (buf) + start;
  else
    scm_misc_error (NULL, "Invalid write access of chars of narrow string: ~s",
                    scm_list_1 (str));
}

/* Unlock the string mutex that was locked when
   scm_i_string_start_writing was called.  */
void
scm_i_string_stop_writing (void)
{
  scm_i_pthread_mutex_unlock (&stringbuf_write_mutex);
}

/* Return the Xth character of STR as a UCS-4 codepoint.  */
scm_t_wchar
scm_i_string_ref (SCM str, size_t x)
{
  if (scm_i_is_narrow_string (str))
    return (scm_t_wchar) (unsigned char) (scm_i_string_chars (str)[x]);
  else
    return scm_i_string_wide_chars (str)[x];
}

/* Returns index+1 of the first char in STR that matches C, or
   0 if the char is not found.  */
int
scm_i_string_contains_char (SCM str, char ch)
{
  size_t i;
  size_t len = scm_i_string_length (str);

  i = 0;
  if (scm_i_is_narrow_string (str))
    {
      while (i < len)
        {
          if (scm_i_string_chars (str)[i] == ch)
            return i+1;
          i++;
        }
    }
  else
    {
      while (i < len)
        {
          if (scm_i_string_wide_chars (str)[i]
              == (unsigned char) ch)
            return i+1;
          i++;
        }
    }
  return 0;
}

int
scm_i_string_strcmp (SCM sstr, size_t start_x, const char *cstr)
{
  if (scm_i_is_narrow_string (sstr))
    {
      const char *a = scm_i_string_chars (sstr) + start_x;
      const char *b = cstr;
      return strncmp (a, b, strlen(b));
    }
  else
    {
      size_t i;
      const scm_t_wchar *a = scm_i_string_wide_chars (sstr) + start_x;
      const char *b = cstr;
      for (i = 0; i < strlen (b); i++)
        {
          if (a[i] != (unsigned char) b[i])
            return 1;
        }
    }
  return 0;
}

/* Set the Pth character of STR to UCS-4 codepoint CHR. */
void
scm_i_string_set_x (SCM str, size_t p, scm_t_wchar chr)
{
  if (chr > 0xFF && scm_i_is_narrow_string (str))
    SET_STRING_STRINGBUF (str, wide_stringbuf (STRING_STRINGBUF (str)));

  if (scm_i_is_narrow_string (str))
    {
      char *dst = scm_i_string_writable_chars (str);
      dst[p] = chr;
    }
  else
    {
      scm_t_wchar *dst = scm_i_string_writable_wide_chars (str);
      dst[p] = chr;
    }
}

/* Symbols.

   Basic symbol creation and accessing is done here, the rest is in
   symbols.[hc].  This has been done to keep stringbufs and the
   internals of strings and string-like objects confined to this file.
*/

#define SYMBOL_STRINGBUF SCM_CELL_OBJECT_1

SCM
scm_i_make_symbol (SCM name, scm_t_bits flags,
		   unsigned long hash, SCM props)
{
  SCM buf;
  size_t start = STRING_START (name);
  size_t length = STRING_LENGTH (name);

  if (IS_SH_STRING (name))
    {
      name = SH_STRING_STRING (name);
      start += STRING_START (name);
    }
  buf = SYMBOL_STRINGBUF (name);

  if (start == 0 && length == STRINGBUF_LENGTH (buf))
    {
      /* reuse buf. */
      scm_i_pthread_mutex_lock (&stringbuf_write_mutex);
      SET_STRINGBUF_SHARED (buf);
      scm_i_pthread_mutex_unlock (&stringbuf_write_mutex);
    }
  else
    {
      /* make new buf. */
      if (scm_i_is_narrow_string (name))
        {
          SCM new_buf = make_stringbuf (length);
          memcpy (STRINGBUF_CHARS (new_buf),
                  STRINGBUF_CHARS (buf) + start, length);
          buf = new_buf;
        }
      else
        {
          SCM new_buf = make_wide_stringbuf (length);
          u32_cpy ((scm_t_uint32 *) STRINGBUF_WIDE_CHARS (new_buf),
                   (scm_t_uint32 *) STRINGBUF_WIDE_CHARS (buf) + start,
                   length);
          buf = new_buf;
        }
    }
  return scm_double_cell (scm_tc7_symbol | flags, SCM_UNPACK (buf),
			  (scm_t_bits) hash, SCM_UNPACK (props));
}

SCM
scm_i_c_make_symbol (const char *name, size_t len,
		     scm_t_bits flags, unsigned long hash, SCM props)
{
  SCM buf = make_stringbuf (len);
  memcpy (STRINGBUF_CHARS (buf), name, len);

  return scm_immutable_double_cell (scm_tc7_symbol | flags, SCM_UNPACK (buf),
				    (scm_t_bits) hash, SCM_UNPACK (props));
}

/* Returns the number of characters in SYM.  This may be different
   from the memory size of SYM.  */
size_t
scm_i_symbol_length (SCM sym)
{
  return STRINGBUF_LENGTH (SYMBOL_STRINGBUF (sym));
}

size_t
scm_c_symbol_length (SCM sym)
#define FUNC_NAME "scm_c_symbol_length"
{
  SCM_VALIDATE_SYMBOL (1, sym);

  return STRINGBUF_LENGTH (SYMBOL_STRINGBUF (sym));
}
#undef FUNC_NAME

/* True if the name of SYM is stored as a Latin-1 encoded string.
   False if it is stored as a 32-bit UCS-4-encoded string.  */
int
scm_i_is_narrow_symbol (SCM sym)
{
  SCM buf;

  buf = SYMBOL_STRINGBUF (sym);
  return !STRINGBUF_WIDE (buf);
}

/* Returns a pointer to the 8-bit Latin-1 encoded character array that
   contains the name of SYM.  */
const char *
scm_i_symbol_chars (SCM sym)
{
  SCM buf;

  buf = SYMBOL_STRINGBUF (sym);
  if (!STRINGBUF_WIDE (buf))
    return (const char *) STRINGBUF_CHARS (buf);
  else
    scm_misc_error (NULL, "Invalid access of chars of a wide symbol ~S",
                    scm_list_1 (sym));
}

/* Return a pointer to the 32-bit UCS-4-encoded character array of a
   symbol's name.  */
const scm_t_wchar *
scm_i_symbol_wide_chars (SCM sym)
{
  SCM buf;

  buf = SYMBOL_STRINGBUF (sym);
  if (STRINGBUF_WIDE (buf))
    return (const scm_t_wchar *) STRINGBUF_WIDE_CHARS (buf);
  else
    scm_misc_error (NULL, "Invalid access of chars of a narrow symbol ~S",
                    scm_list_1 (sym));
}

SCM
scm_i_symbol_substring (SCM sym, size_t start, size_t end)
{
  SCM buf = SYMBOL_STRINGBUF (sym);
  scm_i_pthread_mutex_lock (&stringbuf_write_mutex);
  SET_STRINGBUF_SHARED (buf);
  scm_i_pthread_mutex_unlock (&stringbuf_write_mutex);
  return scm_double_cell (RO_STRING_TAG, SCM_UNPACK (buf),
			  (scm_t_bits)start, (scm_t_bits) end - start);
}

/* Returns the Xth character of symbol SYM as a UCS-4 codepoint.  */
scm_t_wchar
scm_i_symbol_ref (SCM sym, size_t x)
{
  if (scm_i_is_narrow_symbol (sym))
    return (scm_t_wchar) (unsigned char) (scm_i_symbol_chars (sym)[x]);
  else
    return scm_i_symbol_wide_chars (sym)[x];
}

/* Debugging
 */

SCM_DEFINE (scm_sys_string_dump, "%string-dump", 1, 0, 0, (SCM str),
            "Returns an association list containing debugging information\n"
            "for @var{str}. The association list has the following entries."
            "@table @code\n"
            "@item string\n"
            "The string itself.\n"
            "@item start\n"
            "The start index of the string into its stringbuf\n"
            "@item length\n"
            "The length of the string\n"
            "@item shared\n"
            "If this string is a substring, it returns its parent string.\n"
            "Otherwise, it returns @code{#f}\n"
            "@item read-only\n"
            "@code{#t} if the string is read-only\n"
            "@item stringbuf-chars\n"
            "A new string containing this string's stringbuf's characters\n"
            "@item stringbuf-length\n"
            "The number of characters in this stringbuf\n"
            "@item stringbuf-shared\n"
            "@code{#t} if this stringbuf is shared\n"
            "@item stringbuf-wide\n"
            "@code{#t} if this stringbuf's characters are stored in a\n"
            "32-bit buffer, or @code{#f} if they are stored in an 8-bit\n"
            "buffer\n"
            "@end table")
#define FUNC_NAME s_scm_sys_string_dump
{
  SCM e1, e2, e3, e4, e5, e6, e7, e8, e9;
  SCM buf;
  SCM_VALIDATE_STRING (1, str);

  /* String info */
  e1 = scm_cons (scm_from_locale_symbol ("string"),
                 str);
  e2 = scm_cons (scm_from_locale_symbol ("start"),
                 scm_from_size_t (STRING_START (str)));
  e3 = scm_cons (scm_from_locale_symbol ("length"),
                 scm_from_size_t (STRING_LENGTH (str)));

  if (IS_SH_STRING (str))
    {
      e4 = scm_cons (scm_from_locale_symbol ("shared"),
                     SH_STRING_STRING (str));
      buf = STRING_STRINGBUF (SH_STRING_STRING (str));
    }
  else
    {
      e4 = scm_cons (scm_from_locale_symbol ("shared"),
                     SCM_BOOL_F);
      buf = STRING_STRINGBUF (str);
    }

  if (IS_RO_STRING (str))
    e5 = scm_cons (scm_from_locale_symbol ("read-only"),
                   SCM_BOOL_T);
  else
    e5 = scm_cons (scm_from_locale_symbol ("read-only"),
                   SCM_BOOL_F);

  /* Stringbuf info */
  if (!STRINGBUF_WIDE (buf))
    {
      size_t len = STRINGBUF_LENGTH (buf);
      char *cbuf;
      SCM sbc = scm_i_make_string (len, &cbuf);
      memcpy (cbuf, STRINGBUF_CHARS (buf), len);
      e6 = scm_cons (scm_from_locale_symbol ("stringbuf-chars"),
                     sbc);
    }
  else
    {
      size_t len = STRINGBUF_LENGTH (buf);
      scm_t_wchar *cbuf;
      SCM sbc = scm_i_make_wide_string (len, &cbuf);
      u32_cpy ((scm_t_uint32 *) cbuf,
               (scm_t_uint32 *) STRINGBUF_WIDE_CHARS (buf), len);
      e6 = scm_cons (scm_from_locale_symbol ("stringbuf-chars"),
                     sbc);
    }
  e7 = scm_cons (scm_from_locale_symbol ("stringbuf-length"),
                 scm_from_size_t (STRINGBUF_LENGTH (buf)));
  if (STRINGBUF_SHARED (buf))
    e8 = scm_cons (scm_from_locale_symbol ("stringbuf-shared"),
                   SCM_BOOL_T);
  else
    e8 = scm_cons (scm_from_locale_symbol ("stringbuf-shared"),
                   SCM_BOOL_F);
  if (STRINGBUF_WIDE (buf))
    e9 = scm_cons (scm_from_locale_symbol ("stringbuf-wide"),
		   SCM_BOOL_T);
  else
    e9 = scm_cons (scm_from_locale_symbol ("stringbuf-wide"),
		   SCM_BOOL_F);

  return scm_list_n (e1, e2, e3, e4, e5, e6, e7, e8, e9, SCM_UNDEFINED);
}
#undef FUNC_NAME

SCM_DEFINE (scm_sys_symbol_dump, "%symbol-dump", 1, 0, 0, (SCM sym),
            "Returns an association list containing debugging information\n"
            "for @var{sym}. The association list has the following entries."
            "@table @code\n"
            "@item symbol\n"
            "The symbol itself\n"
            "@item hash\n"
            "Its hash value\n"
            "@item interned\n"
            "@code{#t} if it is an interned symbol\n"
            "@item stringbuf-chars\n"
            "A new string containing this symbols's stringbuf's characters\n"
            "@item stringbuf-length\n"
            "The number of characters in this stringbuf\n"
            "@item stringbuf-shared\n"
            "@code{#t} if this stringbuf is shared\n"
            "@item stringbuf-wide\n"
            "@code{#t} if this stringbuf's characters are stored in a\n"
            "32-bit buffer, or @code{#f} if they are stored in an 8-bit\n"
            "buffer\n"
            "@end table")
#define FUNC_NAME s_scm_sys_symbol_dump
{
  SCM e1, e2, e3, e4, e5, e6, e7;
  SCM buf;
  SCM_VALIDATE_SYMBOL (1, sym);
  e1 = scm_cons (scm_from_locale_symbol ("symbol"),
                 sym);
  e2 = scm_cons (scm_from_locale_symbol ("hash"),
                 scm_from_ulong (scm_i_symbol_hash (sym)));
  e3 = scm_cons (scm_from_locale_symbol ("interned"),
                 scm_symbol_interned_p (sym));
  buf = SYMBOL_STRINGBUF (sym);

  /* Stringbuf info */
  if (!STRINGBUF_WIDE (buf))
    {
      size_t len = STRINGBUF_LENGTH (buf);
      char *cbuf;
      SCM sbc = scm_i_make_string (len, &cbuf);
      memcpy (cbuf, STRINGBUF_CHARS (buf), len);
      e4 = scm_cons (scm_from_locale_symbol ("stringbuf-chars"),
                     sbc);
    }
  else
    {
      size_t len = STRINGBUF_LENGTH (buf);
      scm_t_wchar *cbuf;
      SCM sbc = scm_i_make_wide_string (len, &cbuf);
      u32_cpy ((scm_t_uint32 *) cbuf,
               (scm_t_uint32 *) STRINGBUF_WIDE_CHARS (buf), len);
      e4 = scm_cons (scm_from_locale_symbol ("stringbuf-chars"),
                     sbc);
    }
  e5 = scm_cons (scm_from_locale_symbol ("stringbuf-length"),
                 scm_from_size_t (STRINGBUF_LENGTH (buf)));
  if (STRINGBUF_SHARED (buf))
    e6 = scm_cons (scm_from_locale_symbol ("stringbuf-shared"),
                   SCM_BOOL_T);
  else
    e6 = scm_cons (scm_from_locale_symbol ("stringbuf-shared"),
                   SCM_BOOL_F);
  if (STRINGBUF_WIDE (buf))
    e7 = scm_cons (scm_from_locale_symbol ("stringbuf-wide"),
                    SCM_BOOL_T);
  else
    e7 = scm_cons (scm_from_locale_symbol ("stringbuf-wide"),
                    SCM_BOOL_F);
  return scm_list_n (e1, e2, e3, e4, e5, e6, e7, SCM_UNDEFINED);

}
#undef FUNC_NAME

#ifdef SCM_STRING_LENGTH_HISTOGRAM

SCM_DEFINE (scm_sys_stringbuf_hist, "%stringbuf-hist", 0, 0, 0, (void), "")
#define FUNC_NAME s_scm_sys_stringbuf_hist
{
  int i;
  for (i = 0; i < 1000; i++)
    if (lenhist[i])
      fprintf (stderr, " %3d: %u\n", i, lenhist[i]);
  fprintf (stderr, ">999: %u\n", lenhist[1000]);
  return SCM_UNSPECIFIED;
}
#undef FUNC_NAME

#endif

SCM_DEFINE (scm_string_p, "string?", 1, 0, 0,
	    (SCM obj),
	    "Return @code{#t} if @var{obj} is a string, else @code{#f}.")
#define FUNC_NAME s_scm_string_p
{
  return scm_from_bool (IS_STRING (obj));
}
#undef FUNC_NAME

SCM_REGISTER_PROC (s_scm_list_to_string, "list->string", 1, 0, 0, scm_string);

SCM_DEFINE (scm_string, "string", 0, 0, 1,
            (SCM chrs),
	    "@deffnx {Scheme Procedure} list->string chrs\n"
	    "Return a newly allocated string composed of the arguments,\n"
	    "@var{chrs}.")
#define FUNC_NAME s_scm_string
{
  SCM result = SCM_BOOL_F;
  SCM rest;
  size_t len;
  size_t p = 0;
  long i;
  int wide = 0;

  /* Verify that this is a list of chars.  */
  i = scm_ilength (chrs);
  SCM_ASSERT (i >= 0, chrs, SCM_ARG1, FUNC_NAME);

  len = (size_t) i;
  rest = chrs;

  while (len > 0 && scm_is_pair (rest))
    {
      SCM elt = SCM_CAR (rest);
      SCM_VALIDATE_CHAR (SCM_ARGn, elt);
      if (SCM_CHAR (elt) > 0xFF)
        wide = 1;
      rest = SCM_CDR (rest);
      len--;
      scm_remember_upto_here_1 (elt);
    }

  /* Construct a string containing this list of chars.  */
  len = (size_t) i;
  rest = chrs;

  if (wide == 0)
    {
      char *buf;

      result = scm_i_make_string (len, NULL);
      result = scm_i_string_start_writing (result);
      buf = scm_i_string_writable_chars (result);
      while (len > 0 && scm_is_pair (rest))
        {
          SCM elt = SCM_CAR (rest);
          buf[p] = (unsigned char) SCM_CHAR (elt);
          p++;
          rest = SCM_CDR (rest);
          len--;
          scm_remember_upto_here_1 (elt);
        }
    }
  else
    {
      scm_t_wchar *buf;

      result = scm_i_make_wide_string (len, NULL);
      result = scm_i_string_start_writing (result);
      buf = scm_i_string_writable_wide_chars (result);
      while (len > 0 && scm_is_pair (rest))
        {
          SCM elt = SCM_CAR (rest);
          buf[p] = SCM_CHAR (elt);
          p++;
          rest = SCM_CDR (rest);
          len--;
          scm_remember_upto_here_1 (elt);
        }
    }
  scm_i_string_stop_writing ();

  if (len > 0)
    scm_misc_error (NULL, "list changed while constructing string", SCM_EOL);
  if (!scm_is_null (rest))
    scm_wrong_type_arg_msg (NULL, 0, chrs, "proper list");

  return result;
}
#undef FUNC_NAME

SCM_DEFINE (scm_make_string, "make-string", 1, 1, 0,
            (SCM k, SCM chr),
	    "Return a newly allocated string of\n"
            "length @var{k}.  If @var{chr} is given, then all elements of\n"
	    "the string are initialized to @var{chr}, otherwise the contents\n"
	    "of the @var{string} are unspecified.")
#define FUNC_NAME s_scm_make_string
{
  return scm_c_make_string (scm_to_size_t (k), chr);
}
#undef FUNC_NAME

SCM
scm_c_make_string (size_t len, SCM chr)
#define FUNC_NAME NULL
{
  size_t p;
  SCM res = scm_i_make_string (len, NULL);

  if (!SCM_UNBNDP (chr))
    {
      SCM_VALIDATE_CHAR (0, chr);
      res = scm_i_string_start_writing (res);
      for (p = 0; p < len; p++)
        scm_i_string_set_x (res, p, SCM_CHAR (chr));
      scm_i_string_stop_writing ();
    }

  return res;
}
#undef FUNC_NAME

SCM_DEFINE (scm_string_length, "string-length", 1, 0, 0,
	    (SCM string),
	    "Return the number of characters in @var{string}.")
#define FUNC_NAME s_scm_string_length
{
  SCM_VALIDATE_STRING (1, string);
  return scm_from_size_t (STRING_LENGTH (string));
}
#undef FUNC_NAME

SCM_DEFINE (scm_string_bytes_per_char, "string-bytes-per-char", 1, 0, 0,
            (SCM string),
            "Return the bytes used to represent a character in @var{string}."
            "This will return 1 or 4.")
#define FUNC_NAME s_scm_string_bytes_per_char
{
  SCM_VALIDATE_STRING (1, string);
  if (!scm_i_is_narrow_string (string))
    return scm_from_int (4);

  return scm_from_int (1);
}
#undef FUNC_NAME

size_t
scm_c_string_length (SCM string)
{
  if (!IS_STRING (string))
    scm_wrong_type_arg_msg (NULL, 0, string, "string");
  return STRING_LENGTH (string);
}

SCM_DEFINE (scm_string_ref, "string-ref", 2, 0, 0,
            (SCM str, SCM k),
            "Return character @var{k} of @var{str} using zero-origin\n"
            "indexing. @var{k} must be a valid index of @var{str}.")
#define FUNC_NAME s_scm_string_ref
{
  size_t len;
  unsigned long idx;

  SCM_VALIDATE_STRING (1, str);

  len = scm_i_string_length (str);
  if (SCM_LIKELY (len > 0))
    idx = scm_to_unsigned_integer (k, 0, len - 1);
  else
    scm_out_of_range (NULL, k);

  if (scm_i_is_narrow_string (str))
    return SCM_MAKE_CHAR (scm_i_string_chars (str)[idx]);
  else
    return SCM_MAKE_CHAR (scm_i_string_wide_chars (str)[idx]);
}
#undef FUNC_NAME

SCM
scm_c_string_ref (SCM str, size_t p)
{
  if (p >= scm_i_string_length (str))
    scm_out_of_range (NULL, scm_from_size_t (p));
  if (scm_i_is_narrow_string (str))
    return SCM_MAKE_CHAR (scm_i_string_chars (str)[p]);
  else
    return SCM_MAKE_CHAR (scm_i_string_wide_chars (str)[p]);

}

SCM_DEFINE (scm_string_set_x, "string-set!", 3, 0, 0,
            (SCM str, SCM k, SCM chr),
            "Store @var{chr} in element @var{k} of @var{str} and return\n"
            "an unspecified value. @var{k} must be a valid index of\n"
            "@var{str}.")
#define FUNC_NAME s_scm_string_set_x
{
  size_t len;
  unsigned long idx;

  SCM_VALIDATE_STRING (1, str);

  len = scm_i_string_length (str);
  if (SCM_LIKELY (len > 0))
    idx = scm_to_unsigned_integer (k, 0, len - 1);
  else
    scm_out_of_range (NULL, k);

  SCM_VALIDATE_CHAR (3, chr);
  str = scm_i_string_start_writing (str);
  scm_i_string_set_x (str, idx, SCM_CHAR (chr));
  scm_i_string_stop_writing ();

  return SCM_UNSPECIFIED;
}
#undef FUNC_NAME

void
scm_c_string_set_x (SCM str, size_t p, SCM chr)
{
  if (p >= scm_i_string_length (str))
    scm_out_of_range (NULL, scm_from_size_t (p));
  str = scm_i_string_start_writing (str);
  scm_i_string_set_x (str, p, SCM_CHAR (chr));
  scm_i_string_stop_writing ();
}

SCM_DEFINE (scm_substring, "substring", 2, 1, 0,
	    (SCM str, SCM start, SCM end),
	    "Return a newly allocated string formed from the characters\n"
            "of @var{str} beginning with index @var{start} (inclusive) and\n"
	    "ending with index @var{end} (exclusive).\n"
            "@var{str} must be a string, @var{start} and @var{end} must be\n"
	    "exact integers satisfying:\n\n"
            "0 <= @var{start} <= @var{end} <= (string-length @var{str}).")
#define FUNC_NAME s_scm_substring
{
  size_t len, from, to;

  SCM_VALIDATE_STRING (1, str);
  len = scm_i_string_length (str);
  from = scm_to_unsigned_integer (start, 0, len);
  if (SCM_UNBNDP (end))
    to = len;
  else
    to = scm_to_unsigned_integer (end, from, len);
  return scm_i_substring (str, from, to);
}
#undef FUNC_NAME

SCM_DEFINE (scm_substring_read_only, "substring/read-only", 2, 1, 0,
	    (SCM str, SCM start, SCM end),
	    "Return a newly allocated string formed from the characters\n"
            "of @var{str} beginning with index @var{start} (inclusive) and\n"
	    "ending with index @var{end} (exclusive).\n"
            "@var{str} must be a string, @var{start} and @var{end} must be\n"
	    "exact integers satisfying:\n"
	    "\n"
            "0 <= @var{start} <= @var{end} <= (string-length @var{str}).\n"
	    "\n"
	    "The returned string is read-only.\n")
#define FUNC_NAME s_scm_substring_read_only
{
  size_t len, from, to;

  SCM_VALIDATE_STRING (1, str);
  len = scm_i_string_length (str);
  from = scm_to_unsigned_integer (start, 0, len);
  if (SCM_UNBNDP (end))
    to = len;
  else
    to = scm_to_unsigned_integer (end, from, len);
  return scm_i_substring_read_only (str, from, to);
}
#undef FUNC_NAME

SCM_DEFINE (scm_substring_copy, "substring/copy", 2, 1, 0,
	    (SCM str, SCM start, SCM end),
	    "Return a newly allocated string formed from the characters\n"
            "of @var{str} beginning with index @var{start} (inclusive) and\n"
	    "ending with index @var{end} (exclusive).\n"
            "@var{str} must be a string, @var{start} and @var{end} must be\n"
	    "exact integers satisfying:\n\n"
            "0 <= @var{start} <= @var{end} <= (string-length @var{str}).")
#define FUNC_NAME s_scm_substring_copy
{
  /* For the Scheme version, START is mandatory, but for the C
     version, it is optional.  See scm_string_copy in srfi-13.c for a
     rationale.
  */

  size_t from, to;

  SCM_VALIDATE_STRING (1, str);
  scm_i_get_substring_spec (scm_i_string_length (str),
			    start, &from, end, &to);
  return scm_i_substring_copy (str, from, to);
}
#undef FUNC_NAME

SCM_DEFINE (scm_substring_shared, "substring/shared", 2, 1, 0,
	    (SCM str, SCM start, SCM end),
	    "Return string that indirectly refers to the characters\n"
            "of @var{str} beginning with index @var{start} (inclusive) and\n"
	    "ending with index @var{end} (exclusive).\n"
            "@var{str} must be a string, @var{start} and @var{end} must be\n"
	    "exact integers satisfying:\n\n"
            "0 <= @var{start} <= @var{end} <= (string-length @var{str}).")
#define FUNC_NAME s_scm_substring_shared
{
  size_t len, from, to;

  SCM_VALIDATE_STRING (1, str);
  len = scm_i_string_length (str);
  from = scm_to_unsigned_integer (start, 0, len);
  if (SCM_UNBNDP (end))
    to = len;
  else
    to = scm_to_unsigned_integer (end, from, len);
  return scm_i_substring_shared (str, from, to);
}
#undef FUNC_NAME

SCM_DEFINE (scm_string_append, "string-append", 0, 0, 1,
            (SCM args),
            "Return a newly allocated string whose characters form the\n"
            "concatenation of the given strings, @var{args}.")
#define FUNC_NAME s_scm_string_append
{
  SCM res;
  size_t len = 0;
  int wide = 0;
  SCM l, s;
  size_t i;
  union
  {
    char *narrow;
    scm_t_wchar *wide;
  } data;

  SCM_VALIDATE_REST_ARGUMENT (args);
  for (l = args; !scm_is_null (l); l = SCM_CDR (l))
    {
      s = SCM_CAR (l);
      SCM_VALIDATE_STRING (SCM_ARGn, s);
      len += scm_i_string_length (s);
      if (!scm_i_is_narrow_string (s))
        wide = 1;
    }
  data.narrow = NULL;
  if (!wide)
    res = scm_i_make_string (len, &data.narrow);
  else
    res = scm_i_make_wide_string (len, &data.wide);

  for (l = args; !scm_is_null (l); l = SCM_CDR (l))
    {
      size_t len;
      s = SCM_CAR (l);
      SCM_VALIDATE_STRING (SCM_ARGn, s);
      len = scm_i_string_length (s);
      if (!wide)
        {
          memcpy (data.narrow, scm_i_string_chars (s), len);
          data.narrow += len;
        }
      else
        {
          if (scm_i_is_narrow_string (s))
            {
              for (i = 0; i < scm_i_string_length (s); i++)
                data.wide[i] = (unsigned char) scm_i_string_chars (s)[i];
            }
          else
            u32_cpy ((scm_t_uint32 *) data.wide,
                     (scm_t_uint32 *) scm_i_string_wide_chars (s), len);
          data.wide += len;
        }
      scm_remember_upto_here_1 (s);
    }
  return res;
}
#undef FUNC_NAME

int
scm_is_string (SCM obj)
{
  return IS_STRING (obj);
}

/* Conversion to/from other encodings.  */

SCM_SYMBOL (scm_encoding_error_key, "encoding-error");
void
scm_encoding_error (const char *subr, int err, const char *message,
		    const char *from, const char *to, SCM string_or_bv)
{
  /* Raise an exception that conveys all the information needed to debug the
     problem.  Only perform locale conversions that are safe; in particular,
     don't try to display STRING_OR_BV when it's a string since converting it to
     the output locale may fail.  */
  scm_throw (scm_encoding_error_key,
	     scm_list_n (scm_from_locale_string (subr),
			 scm_from_locale_string (message),
			 scm_from_int (err),
			 scm_from_locale_string (from),
			 scm_from_locale_string (to),
			 string_or_bv,
			 SCM_UNDEFINED));
}

SCM
scm_from_stringn (const char *str, size_t len, const char *encoding,
                  scm_t_string_failed_conversion_handler handler)
{
  size_t u32len, i;
  scm_t_wchar *u32;
  int wide = 0;
  SCM res;

  if (len == 0)
    return scm_nullstr;

  if (encoding == NULL)
    {
      /* If encoding is null, use Latin-1.  */
      char *buf;
      res = scm_i_make_string (len, &buf);
      memcpy (buf, str, len);
      return res;
    }

  u32len = 0;
  u32 = (scm_t_wchar *) u32_conv_from_encoding (encoding,
                                                (enum iconv_ilseq_handler)
                                                handler,
                                                str, len,
                                                NULL,
                                                NULL, &u32len);

  if (SCM_UNLIKELY (u32 == NULL))
    {
      /* Raise an error and pass the raw C string as a bytevector to the `throw'
	 handler.  */
      SCM bv;
      signed char *buf;

      buf = scm_gc_malloc_pointerless (len, "bytevector");
      memcpy (buf, str, len);
      bv = scm_c_take_bytevector (buf, len);

      scm_encoding_error (__func__, errno,
			  "input locale conversion error",
			  encoding, "UTF-32", bv);
    }

  i = 0;
  while (i < u32len)
    if (u32[i++] > 0xFF)
      {
        wide = 1;
        break;
      }

  if (!wide)
    {
      char *dst;
      res = scm_i_make_string (u32len, &dst);
      for (i = 0; i < u32len; i ++)
        dst[i] = (unsigned char) u32[i];
      dst[u32len] = '\0';
    }
  else
    {
      scm_t_wchar *wdst;
      res = scm_i_make_wide_string (u32len, &wdst);
      u32_cpy ((scm_t_uint32 *) wdst, (scm_t_uint32 *) u32, u32len);
      wdst[u32len] = 0;
    }

  free (u32);
  return res;
}

SCM
scm_from_locale_stringn (const char *str, size_t len)
{
  const char *enc;
  scm_t_string_failed_conversion_handler hndl;
  SCM inport;
  scm_t_port *pt;

  if (len == (size_t) -1)
    len = strlen (str);
  if (len == 0)
    return scm_nullstr;

  inport = scm_current_input_port ();
  if (!SCM_UNBNDP (inport) && SCM_OPINPORTP (inport))
    {
      pt = SCM_PTAB_ENTRY (inport);
      enc = pt->encoding;
      hndl = pt->ilseq_handler;
    }
  else
    {
      enc = NULL;
      hndl = SCM_FAILED_CONVERSION_ERROR;
    }

  return scm_from_stringn (str, len, enc, hndl);
}

SCM
scm_from_locale_string (const char *str)
{
  if (str == NULL)
    return scm_nullstr;

  return scm_from_locale_stringn (str, -1);
}

SCM
scm_i_from_utf8_string (const scm_t_uint8 *str)
{
  return scm_from_stringn ((const char *) str,
                           strlen ((char *) str), "UTF-8",
                           SCM_FAILED_CONVERSION_ERROR);
}

/* Create a new scheme string from the C string STR.  The memory of
   STR may be used directly as storage for the new string.  */
/* FIXME: GC-wise, the only way to use the memory area pointed to by STR
   would be to register a finalizer to eventually free(3) STR, which isn't
   worth it.  Should we just deprecate the `scm_take_' functions?  */
SCM
scm_take_locale_stringn (char *str, size_t len)
{
  SCM res;

  res = scm_from_locale_stringn (str, len);
  free (str);

  return res;
}

SCM
scm_take_locale_string (char *str)
{
  return scm_take_locale_stringn (str, -1);
}

/* Change libunistring escapes (\uXXXX and \UXXXXXXXX) to \xXX \uXXXX
   and \UXXXXXX.  */
static void
unistring_escapes_to_guile_escapes (char **bufp, size_t *lenp)
{
  char *before, *after;
  size_t i, j;

  before = *bufp;
  after = *bufp;
  i = 0;
  j = 0;
  while (i < *lenp)
    {
      if ((i <= *lenp - 6)
          && before[i] == '\\'
          && before[i + 1] == 'u'
          && before[i + 2] == '0' && before[i + 3] == '0')
        {
          /* Convert \u00NN to \xNN */
          after[j] = '\\';
          after[j + 1] = 'x';
          after[j + 2] = tolower ((int) before[i + 4]);
          after[j + 3] = tolower ((int) before[i + 5]);
          i += 6;
          j += 4;
        }
      else if ((i <= *lenp - 10)
               && before[i] == '\\'
               && before[i + 1] == 'U'
               && before[i + 2] == '0' && before[i + 3] == '0')
        {
          /* Convert \U00NNNNNN to \UNNNNNN */
          after[j] = '\\';
          after[j + 1] = 'U';
          after[j + 2] = tolower ((int) before[i + 4]);
          after[j + 3] = tolower ((int) before[i + 5]);
          after[j + 4] = tolower ((int) before[i + 6]);
          after[j + 5] = tolower ((int) before[i + 7]);
          after[j + 6] = tolower ((int) before[i + 8]);
          after[j + 7] = tolower ((int) before[i + 9]);
          i += 10;
          j += 8;
        }
      else
        {
          after[j] = before[i];
          i++;
          j++;
        }
    }
  *lenp = j;
  after = scm_realloc (after, j);
}

/* Change libunistring escapes (\uXXXX and \UXXXXXXXX) to \xXXXX; */
static void
unistring_escapes_to_r6rs_escapes (char **bufp, size_t *lenp)
{
  char *before, *after;
  size_t i, j;
  /* The worst case is if the input string contains all 4-digit hex escapes.
     "\uXXXX" (six characters) becomes "\xXXXX;" (seven characters) */
  size_t max_out_len = (*lenp * 7) / 6 + 1;
  size_t nzeros, ndigits;

  before = *bufp;
  after = alloca (max_out_len);
  i = 0;
  j = 0;
  while (i < *lenp)
    {
      if (((i <= *lenp - 6) && before[i] == '\\' && before[i + 1] == 'u')
          || ((i <= *lenp - 10) && before[i] == '\\' && before[i + 1] == 'U'))
        {
          if (before[i + 1] == 'u')
            ndigits = 4;
          else if (before[i + 1] == 'U')
            ndigits = 8;
          else
            abort ();

          /* Add the R6RS hex escape initial sequence.  */
          after[j] = '\\';
          after[j + 1] = 'x';

          /* Move string positions to the start of the hex numbers.  */
          i += 2;
          j += 2;

          /* Find the number of initial zeros in this hex number.  */
          nzeros = 0;
          while (before[i + nzeros] == '0' && nzeros < ndigits)
            nzeros++;

          /* Copy the number, skipping initial zeros, and then move the string
             positions.  */
          if (nzeros == ndigits)
            {
              after[j] = '0';
              i += ndigits;
              j += 1;
            }
          else
            {
              int pos;
              for (pos = 0; pos < ndigits - nzeros; pos++)
                after[j + pos] = tolower ((int) before[i + nzeros + pos]);
              i += ndigits;
              j += (ndigits - nzeros);
            }

          /* Add terminating semicolon.  */
          after[j] = ';';
          j++;
        }
      else
        {
          after[j] = before[i];
          i++;
          j++;
        }
    }
  *lenp = j;
  before = scm_realloc (before, j);
  memcpy (before, after, j);
}

char *
scm_to_locale_stringn (SCM str, size_t *lenp)
{
  SCM outport;
  scm_t_port *pt;
  const char *enc;

  outport = scm_current_output_port ();
  if (!SCM_UNBNDP (outport) && SCM_OPOUTPORTP (outport))
    {
      pt = SCM_PTAB_ENTRY (outport);
      enc = pt->encoding;
    }
  else
    enc = NULL;

  return scm_to_stringn (str, lenp,
                         enc,
                         scm_i_get_conversion_strategy (SCM_BOOL_F));
}

/* Return a malloc(3)-allocated buffer containing the contents of STR encoded
   according to ENCODING.  If LENP is non-NULL, set it to the size in bytes of
   the returned buffer.  If the conversion to ENCODING fails, apply the strategy
   defined by HANDLER.  */
char *
scm_to_stringn (SCM str, size_t *lenp, const char *encoding,
                scm_t_string_failed_conversion_handler handler)
{
  char *buf;
  size_t ilen, len, i;
  int ret;
  const char *enc;

  if (!scm_is_string (str))
    scm_wrong_type_arg_msg (NULL, 0, str, "string");
  ilen = scm_i_string_length (str);

  if (ilen == 0)
    {
      buf = scm_malloc (1);
      buf[0] = '\0';
      if (lenp)
        *lenp = 0;
      return buf;
    }

  if (lenp == NULL)
    for (i = 0; i < ilen; i++)
      if (scm_i_string_ref (str, i) == '\0')
        scm_misc_error (NULL,
                        "string contains #\\nul character: ~S",
                        scm_list_1 (str));

  if (scm_i_is_narrow_string (str) && (encoding == NULL))
    {
      /* If using native Latin-1 encoding, just copy the string
         contents.  */
      if (lenp)
        {
          buf = scm_malloc (ilen);
          memcpy (buf, scm_i_string_chars (str), ilen);
          *lenp = ilen;
          return buf;
        }
      else
        {
          buf = scm_malloc (ilen + 1);
          memcpy (buf, scm_i_string_chars (str), ilen);
          buf[ilen] = '\0';
          return buf;
        }
    }

  buf = NULL;
  len = 0;
  enc = encoding;
  if (enc == NULL)
    enc = "ISO-8859-1";
  if (scm_i_is_narrow_string (str))
    {
      ret = mem_iconveh (scm_i_string_chars (str), ilen,
                         "ISO-8859-1", enc,
                         (enum iconv_ilseq_handler) handler, NULL,
                         &buf, &len);

      if (ret != 0)
        scm_encoding_error (__func__, errno,
			    "cannot convert to output locale",
			    "ISO-8859-1", enc, str);
    }
  else
    {
      buf = u32_conv_to_encoding (enc,
                                  (enum iconv_ilseq_handler) handler,
                                  (scm_t_uint32 *) scm_i_string_wide_chars (str),
                                  ilen,
                                  NULL,
                                  NULL, &len);
      if (buf == NULL)
        scm_encoding_error (__func__, errno,
			    "cannot convert to output locale",
			    "UTF-32", enc, str);
    }
  if (handler == SCM_FAILED_CONVERSION_ESCAPE_SEQUENCE)
    {
      if (SCM_R6RS_ESCAPES_P)
        unistring_escapes_to_r6rs_escapes (&buf, &len);
      else
        unistring_escapes_to_guile_escapes (&buf, &len);
    }
  if (lenp)
    *lenp = len;
  else
    {
      buf = scm_realloc (buf, len + 1);
      buf[len] = '\0';
    }

  scm_remember_upto_here_1 (str);
  return buf;
}

char *
scm_to_locale_string (SCM str)
{
  return scm_to_locale_stringn (str, NULL);
}

scm_t_uint8 *
scm_i_to_utf8_string (SCM str)
{
  char *u8str;
  u8str = scm_to_stringn (str, NULL, "UTF-8", SCM_FAILED_CONVERSION_ERROR);
  return (scm_t_uint8 *) u8str;
}

size_t
scm_to_locale_stringbuf (SCM str, char *buf, size_t max_len)
{
  size_t len;
  char *result = NULL;
  if (!scm_is_string (str))
    scm_wrong_type_arg_msg (NULL, 0, str, "string");
  result = scm_to_locale_stringn (str, &len);

  memcpy (buf, result, (len > max_len) ? max_len : len);
  free (result);

  scm_remember_upto_here_1 (str);
  return len;
}

/* Unicode string normalization.  */

/* This function is a partial clone of SCM_STRING_TO_U32_BUF from
   libguile/i18n.c.  It would be useful to have this factored out into a more
   convenient location, but its use of alloca makes that tricky to do. */

static SCM
normalize_str (SCM string, uninorm_t form)
{
  SCM ret;
  scm_t_uint32 *w_str;
  scm_t_wchar *cbuf;
  size_t rlen, len = scm_i_string_length (string);

  if (scm_i_is_narrow_string (string))
    {
      size_t i;
      const char *buf = scm_i_string_chars (string);

      w_str = alloca (sizeof (scm_t_wchar) * (len + 1));

      for (i = 0; i < len; i ++)
	w_str[i] = (unsigned char) buf[i];
      w_str[len] = 0;
    }
  else
    w_str = (scm_t_uint32 *) scm_i_string_wide_chars (string);

  w_str = u32_normalize (form, w_str, len, NULL, &rlen);

  ret = scm_i_make_wide_string (rlen, &cbuf);
  u32_cpy ((scm_t_uint32 *) cbuf, w_str, rlen);
  free (w_str);

  scm_i_try_narrow_string (ret);

  return ret;
}

SCM_DEFINE (scm_string_normalize_nfc, "string-normalize-nfc", 1, 0, 0,
	    (SCM string),
	    "Returns the NFC normalized form of @var{string}.")
#define FUNC_NAME s_scm_string_normalize_nfc
{
  SCM_VALIDATE_STRING (1, string);
  return normalize_str (string, UNINORM_NFC);
}
#undef FUNC_NAME

SCM_DEFINE (scm_string_normalize_nfd, "string-normalize-nfd", 1, 0, 0,
	    (SCM string),
	    "Returns the NFD normalized form of @var{string}.")
#define FUNC_NAME s_scm_string_normalize_nfd
{
  SCM_VALIDATE_STRING (1, string);
  return normalize_str (string, UNINORM_NFD);
}
#undef FUNC_NAME

SCM_DEFINE (scm_string_normalize_nfkc, "string-normalize-nfkc", 1, 0, 0,
	    (SCM string),
	    "Returns the NFKC normalized form of @var{string}.")
#define FUNC_NAME s_scm_string_normalize_nfkc
{
  SCM_VALIDATE_STRING (1, string);
  return normalize_str (string, UNINORM_NFKC);
}
#undef FUNC_NAME

SCM_DEFINE (scm_string_normalize_nfkd, "string-normalize-nfkd", 1, 0, 0,
	    (SCM string),
	    "Returns the NFKD normalized form of @var{string}.")
#define FUNC_NAME s_scm_string_normalize_nfkd
{
  SCM_VALIDATE_STRING (1, string);
  return normalize_str (string, UNINORM_NFKD);
}
#undef FUNC_NAME

/* converts C scm_array of strings to SCM scm_list of strings. */
/* If argc < 0, a null terminated scm_array is assumed. */
SCM
scm_makfromstrs (int argc, char **argv)
{
  int i = argc;
  SCM lst = SCM_EOL;
  if (0 > i)
    for (i = 0; argv[i]; i++);
  while (i--)
    lst = scm_cons (scm_from_locale_string (argv[i]), lst);
  return lst;
}

/* Return a newly allocated array of char pointers to each of the strings
   in args, with a terminating NULL pointer.  */

char **
scm_i_allocate_string_pointers (SCM list)
#define FUNC_NAME "scm_i_allocate_string_pointers"
{
  char **result;
  int len = scm_ilength (list);
  int i;

  if (len < 0)
    scm_wrong_type_arg_msg (NULL, 0, list, "proper list");

  result = scm_gc_malloc ((len + 1) * sizeof (char *),
			  "string pointers");
  result[len] = NULL;

  /* The list might be have been modified in another thread, so
     we check LIST before each access.
   */
  for (i = 0; i < len && scm_is_pair (list); i++)
    {
      SCM str;
      size_t len;

      str = SCM_CAR (list);
      len = scm_c_string_length (str);

      result[i] = scm_gc_malloc_pointerless (len + 1, "string pointers");
      memcpy (result[i], scm_i_string_chars (str), len);
      result[i][len] = '\0';

      list = SCM_CDR (list);
    }

  return result;
}
#undef FUNC_NAME

void
scm_i_get_substring_spec (size_t len,
			  SCM start, size_t *cstart,
			  SCM end, size_t *cend)
{
  if (SCM_UNBNDP (start))
    *cstart = 0;
  else
    *cstart = scm_to_unsigned_integer (start, 0, len);

  if (SCM_UNBNDP (end))
    *cend = len;
  else
    *cend = scm_to_unsigned_integer (end, *cstart, len);
}

#if SCM_ENABLE_DEPRECATED

/* When these definitions are removed, it becomes reasonable to use
   read-only strings for string literals.  For that, change the reader
   to create string literals with scm_c_substring_read_only instead of
   with scm_c_substring_copy.
*/

int
scm_i_deprecated_stringp (SCM str)
{
  scm_c_issue_deprecation_warning
    ("SCM_STRINGP is deprecated.  Use scm_is_string instead.");

  return scm_is_string (str);
}

char *
scm_i_deprecated_string_chars (SCM str)
{
  char *chars;

  scm_c_issue_deprecation_warning
    ("SCM_STRING_CHARS is deprecated.  See the manual for alternatives.");

  /* We don't accept shared substrings here since they are not
     null-terminated.
  */
  if (IS_SH_STRING (str))
    scm_misc_error (NULL,
		    "SCM_STRING_CHARS does not work with shared substrings",
		    SCM_EOL);

  /* We explicitly test for read-only strings to produce a better
     error message.
  */

  if (IS_RO_STRING (str))
    scm_misc_error (NULL,
		    "SCM_STRING_CHARS does not work with read-only strings",
		    SCM_EOL);

  /* The following is still wrong, of course...
   */
  str = scm_i_string_start_writing (str);
  chars = scm_i_string_writable_chars (str);
  scm_i_string_stop_writing ();
  return chars;
}

size_t
scm_i_deprecated_string_length (SCM str)
{
  scm_c_issue_deprecation_warning
    ("SCM_STRING_LENGTH is deprecated.  Use scm_c_string_length instead.");
  return scm_c_string_length (str);
}

#endif

static SCM
string_handle_ref (scm_t_array_handle *h, size_t index)
{
  return scm_c_string_ref (h->array, index);
}

static void
string_handle_set (scm_t_array_handle *h, size_t index, SCM val)
{
  scm_c_string_set_x (h->array, index, val);
}

static void
string_get_handle (SCM v, scm_t_array_handle *h)
{
  h->array = v;
  h->ndims = 1;
  h->dims = &h->dim0;
  h->dim0.lbnd = 0;
  h->dim0.ubnd = scm_c_string_length (v) - 1;
  h->dim0.inc = 1;
  h->element_type = SCM_ARRAY_ELEMENT_TYPE_CHAR;
  h->elements = h->writable_elements = NULL;
}

SCM_ARRAY_IMPLEMENTATION (scm_tc7_string, 0x7f,
                          string_handle_ref, string_handle_set,
                          string_get_handle)
SCM_VECTOR_IMPLEMENTATION (SCM_ARRAY_ELEMENT_TYPE_CHAR, scm_make_string)

void
scm_init_strings ()
{
  scm_nullstr = scm_i_make_string (0, NULL);

#include "libguile/strings.x"
}

/*
  Local Variables:
  c-file-style: "gnu"
  End:
*/