/*  sexp.c -- standalone sexp library implementation          */
/*  Copyright (c) 2009-2015 Alex Shinn.  All rights reserved. */
/*  BSD-style license: http://synthcode.com/license.txt       */

#include "chibi/sexp.h"

/* optional huffman-compressed immediate symbols */
struct sexp_huff_entry {
  unsigned char len;
  unsigned short bits;
};

#if SEXP_USE_HUFF_SYMS
#include "chibi/sexp-hufftabs.h"
#include "chibi/sexp-huff.h"
#endif

#ifdef _WIN32
#include <io.h>
#endif

static int sexp_initialized_p = 0;

static const char sexp_separators[] = {
  /* 1  2  3  4  5  6  7  8  9  a  b  c  d  e  f         */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, /* x0_ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* x1_ */
  1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, /* x2_ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, /* x3_ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* x4_ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, /* x5_ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* x6_ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, /* x7_ */
};

static int digit_value (int c) {
  return (((c)<='9') ? ((c) - '0') : ((sexp_tolower(c) - 'a') + 10));
}

static int hex_digit (int n) {
  return ((n<=9) ? ('0' + n) : ('a' + n - 10));
}

static int is_precision_indicator(int c) {
  return c=='d' || c=='D' || c=='e' || c=='E' || c=='f' || c=='F'
    || c=='l' || c=='L' || c=='s' || c=='S';
}

int sexp_is_separator(int c) {
  return 0<c && c<0x80 && sexp_separators[c];
}

#if SEXP_USE_GLOBAL_SYMBOLS
sexp sexp_symbol_table[SEXP_SYMBOL_TABLE_SIZE];
#endif

#if ! SEXP_USE_UNSAFE_PUSH
sexp sexp_push_op(sexp ctx, sexp* loc, sexp x) {
  sexp tmp = sexp_cons(ctx, x, *loc);
  if (sexp_exceptionp(tmp)) return *loc;
  *loc = tmp;
  return tmp;
}
#endif

sexp sexp_alloc_tagged_aux(sexp ctx, size_t size, sexp_uint_t tag sexp_current_source_param) {
#if SEXP_USE_TRACK_ALLOC_BACKTRACE
  int i;
  void* trace[SEXP_BACKTRACE_SIZE + 1];
#endif
  sexp res = (sexp) sexp_alloc(ctx, size);
  if (res && ! sexp_exceptionp(res)) {
    sexp_pointer_tag(res) = tag;
#if SEXP_USE_TRACK_ALLOC_SOURCE
    sexp_pointer_source(res) = source;
#if SEXP_USE_TRACK_ALLOC_BACKTRACE
    backtrace(trace, SEXP_BACKTRACE_SIZE + 1);
    for (i=0; i<SEXP_BACKTRACE_SIZE; i++) res->backtrace[i] = trace[i+1];
#endif
#endif
#if SEXP_USE_HEADER_MAGIC
    sexp_pointer_magic(res) = SEXP_POINTER_MAGIC;
#endif
  }
  return res;
}

#if SEXP_USE_OBJECT_BRACE_LITERALS
sexp sexp_write_simple_object (sexp ctx, sexp self, sexp_sint_t n, sexp obj, sexp writer, sexp out) {
  sexp t, x;
  sexp_gc_var1(args);
  sexp_sint_t i, len, nulls=0;
  i = sexp_pointer_tag(obj);
  sexp_write_char(ctx, '{', out);
  if (i >= sexp_context_num_types(ctx)) {
    sexp_write_string(ctx, "invalid", out);
  } else {
    sexp_gc_preserve1(ctx, args);
    t = sexp_object_type(ctx, obj);
    sexp_write_string(ctx, sexp_string_data(sexp_type_name(t)), out);
    sexp_write_char(ctx, ' ', out);
    if (sexp_type_id(t) && sexp_truep(sexp_type_id(t))) {
      sexp_write(ctx, sexp_type_id(t), out);
    } else {
      sexp_write_char(ctx, '#', out);
      sexp_write(ctx, sexp_make_fixnum(sexp_type_tag(t)), out);
    }
    len = sexp_type_num_slots_of_object(t, obj);
    args = sexp_list1(ctx, SEXP_FALSE);
    for (i=0; i<len; i++) {
      x = sexp_slot_ref(obj, i);
      if (x) {
        for ( ; nulls; --nulls)
          sexp_write_string(ctx, " #f", out);
        sexp_write_char(ctx, ' ', out);
        if (writer && sexp_applicablep(writer)) {
          sexp_car(args) = x;
          x = sexp_apply(ctx, writer, args);
          if (sexp_exceptionp(x)) sexp_print_exception(ctx, x, out);
        } else {
          sexp_write(ctx, sexp_slot_ref(obj, i), out);
        }
      } else {
        nulls++;
      }
    }
    sexp_gc_release1(ctx);
  }
  sexp_write_char(ctx, '}', out);
  return SEXP_VOID;
}
#else
#define sexp_write_simple_object NULL
#endif

#if SEXP_USE_UNIFORM_VECTOR_LITERALS
sexp sexp_write_uvector(sexp ctx, sexp self, sexp_sint_t n, sexp obj, sexp writer, sexp out) {
  sexp_uint_t i, len;
  char* str;
  sexp_gc_var2(f, tmp);
  sexp_gc_preserve2(ctx, f, tmp);
  f = sexp_make_flonum(ctx, 0.0f);
  sexp_write_char(ctx, '#', out);
  sexp_write_char(ctx, sexp_uvector_prefix(sexp_uvector_type(obj)), out);
  sexp_write(ctx, sexp_make_fixnum(sexp_uvector_element_size(sexp_uvector_type(obj))), out);
  sexp_write_char(ctx, '(', out);
  len = sexp_uvector_length(obj);
  str = (char*) sexp_uvector_data(obj);
  for (i=0; i<(sexp_sint_t)len; i++) {
    if (i!=0) sexp_write_char(ctx, ' ', out);
    switch (sexp_uvector_type(obj)) {
    case SEXP_U1: sexp_write(ctx, sexp_make_fixnum(sexp_bit_ref(obj, i)), out); break;
    case SEXP_S8: sexp_write(ctx, sexp_make_fixnum(((signed char*)str)[i]), out); break;
    case SEXP_S16: sexp_write(ctx, sexp_make_fixnum(((signed short*)str)[i]), out); break;
    case SEXP_U16: sexp_write(ctx, sexp_make_fixnum(((unsigned short*)str)[i]), out); break;
    case SEXP_S32: sexp_write(ctx, tmp=sexp_make_integer(ctx, ((int32_t*)str)[i]), out); break;
    case SEXP_U32: sexp_write(ctx, tmp=sexp_make_unsigned_integer(ctx, ((uint32_t*)str)[i]), out); break;
    case SEXP_S64: sexp_write(ctx, tmp=sexp_make_integer(ctx, ((int64_t*)str)[i]), out); break;
    case SEXP_U64: sexp_write(ctx, tmp=sexp_make_unsigned_integer(ctx, ((uint64_t*)str)[i]), out); break;
#if SEXP_USE_FLONUMS
#if SEXP_USE_MINI_FLOAT_UNIFORM_VECTORS
    case SEXP_F8: sexp_flonum_value_set(f, sexp_quarter_to_double(((unsigned char*)str)[i])); sexp_write(ctx, f, out); break;
    case SEXP_F16: sexp_flonum_value_set(f, sexp_half_to_double(((unsigned short*)str)[i])); sexp_write(ctx, f, out); break;
#endif
    case SEXP_F32: sexp_flonum_value_set(f, ((float*)str)[i]); sexp_write(ctx, f, out); break;
    case SEXP_F64: sexp_flonum_value_set(f, ((double*)str)[i]); sexp_write(ctx, f, out); break;
#endif
#if SEXP_USE_COMPLEX
    case SEXP_C64:
      sexp_flonum_value_set(f, ((float*)str)[i*2]);
      sexp_write(ctx, f, out);
      if (((float*)str)[i*2 + 1] >= 0)
        sexp_write_char(ctx, '+', out);
      sexp_flonum_value_set(f, ((float*)str)[i*2 + 1]);
      sexp_write(ctx, f, out);
      sexp_write_char(ctx, 'i', out);
      break;
    case SEXP_C128:
      sexp_flonum_value_set(f, ((double*)str)[i*2]);
      sexp_write(ctx, f, out);
      if (((double*)str)[i*2 + 1] >= 0)
        sexp_write_char(ctx, '+', out);
      sexp_flonum_value_set(f, ((double*)str)[i*2 + 1]);
      sexp_write(ctx, f, out);
      sexp_write_char(ctx, 'i', out);
      break;
#endif
    }
  }
  sexp_write_char(ctx, ')', out);
  sexp_gc_release2(ctx);
  return SEXP_VOID;
}
#endif

sexp sexp_finalize_fileno (sexp ctx, sexp self, sexp_sint_t n, sexp fileno) {
  if (sexp_fileno_openp(fileno) && !sexp_fileno_no_closep(fileno)) {
    sexp_fileno_openp(fileno) = 0;
    close(sexp_fileno_fd(fileno));
  }
  return SEXP_VOID;
}

sexp sexp_finalize_port (sexp ctx, sexp self, sexp_sint_t n, sexp port) {
  sexp res = SEXP_VOID;
  if (sexp_port_openp(port)) {
    sexp_port_openp(port) = 0;
    if (sexp_oportp(port)) sexp_flush_forced(ctx, port);
#ifndef PLAN9
    if (sexp_filenop(sexp_port_fd(port))
        && sexp_fileno_openp(sexp_port_fd(port))) {
      if (sexp_port_shutdownp(port)) {
        /* shutdown the socket if requested */
        if (sexp_iportp(port))
          shutdown(sexp_port_fileno(port), sexp_oportp(port) ? SHUT_RDWR : SHUT_RD);
        if (sexp_oportp(port))
          shutdown(sexp_port_fileno(port), SHUT_WR);
      }
      if (!sexp_port_no_closep(port)) {
        if (--sexp_fileno_count(sexp_port_fd(port)) == 0)
          sexp_finalize_fileno(ctx, self, n, sexp_port_fd(port));
      }
    }
#endif
    if (sexp_port_stream(port) && ! sexp_port_no_closep(port))
      /* close the stream */
      fclose(sexp_port_stream(port));
    sexp_port_offset(port) = 0;
    sexp_port_size(port) = 0;
  }
  return res;
}

#if SEXP_USE_DL
#ifdef _WIN32
sexp sexp_finalize_dl (sexp ctx, sexp self, sexp_sint_t n, sexp dl) {
  FreeLibrary(sexp_dl_handle(dl));
  return SEXP_VOID;
}
#else
sexp sexp_finalize_dl (sexp ctx, sexp self, sexp_sint_t n, sexp dl) {
  dlclose(sexp_dl_handle(dl));
  return SEXP_VOID;
}
#endif
#endif

#if SEXP_USE_UNIFORM_VECTOR_LITERALS
sexp sexp_finalize_uvector (sexp ctx, sexp self, sexp_sint_t n, sexp obj) {
  /* if (sexp_uvector_freep(obj)) */
  /*   free(sexp_uvector_data(obj)); */
  return SEXP_VOID;
}
#endif

static struct sexp_type_struct _sexp_type_specs[] = {
  {(sexp)"Object", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_OBJECT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Type", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_TYPE, sexp_offsetof(type, name), 9, 9, 0, 0, sexp_sizeof(type), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Integer", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_FIXNUM, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Number", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_NUMBER, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Char", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_CHAR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Boolean", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_BOOLEAN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Pair", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_PAIR, sexp_offsetof(pair, car), 2, 3, 0, 0, sexp_sizeof(pair), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Symbol", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_SYMBOL, 0, 0, 0, 0, 0, sexp_sizeof(symbol)+1, sexp_offsetof(symbol, length), 1, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Byte-Vector", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_BYTES, 0, 0, 0, 0, 0, sexp_sizeof(bytes)+1, sexp_offsetof(bytes, length), 1, 0, 0, 0, 0, 0, 0, NULL},
#if SEXP_USE_PACKED_STRINGS
  {(sexp)"String", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_STRING, 0, 0, 0, 0, 0, sexp_sizeof(string)+1, sexp_offsetof(string, length), 1, 0, 0, 0, 0, 0, 0, NULL},
#else
  {(sexp)"String", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_STRING, sexp_offsetof(string, bytes), 1, 1+SEXP_USE_STRING_INDEX_TABLE, 0, 0, sexp_sizeof(string), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
#endif
  {(sexp)"Vector", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_VECTOR, sexp_sizeof(vector), 0, 0, sexp_offsetof(vector, length), 1, sexp_sizeof(vector), sexp_offsetof(vector, length), sizeof(sexp), 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Flonum", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_FLONUM, 0, 0, 0, 0, 0, sexp_sizeof(flonum), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Bignum", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_BIGNUM, 0, 0, 0, 0, 0, sexp_sizeof(bignum), sexp_offsetof(bignum, length), sizeof(sexp_uint_t), 0, 0, 0, 0, 0, 0, NULL},
#if SEXP_USE_STABLE_ABI || SEXP_USE_RATIOS
  {(sexp)"Ratio", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_RATIO, sexp_offsetof(ratio, numerator), 2, 2, 0, 0, sexp_sizeof(ratio), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
#endif
#if SEXP_USE_STABLE_ABI || SEXP_USE_COMPLEX
  {(sexp)"Complex", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_COMPLEX, sexp_offsetof(complex, real), 2, 2, 0, 0, sexp_sizeof(complex), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
#endif
#if SEXP_USE_STABLE_ABI || SEXP_USE_DISJOINT_STRING_CURSORS
  {(sexp)"String-Cursor", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_STRING_CURSOR, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL},
#endif
  {(sexp)"Input-Port", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, SEXP_FINALIZE_PORTN, SEXP_IPORT, sexp_offsetof(port, name), 3, 3, 0, 0, sexp_sizeof(port), 0, 0, 0, 0, 0, 0, 0, 0, SEXP_FINALIZE_PORT},
  {(sexp)"Output-Port", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, SEXP_FINALIZE_PORTN, SEXP_OPORT, sexp_offsetof(port, name), 3, 3, 0, 0, sexp_sizeof(port), 0, 0, 0, 0, 0, 0, 0, 0, SEXP_FINALIZE_PORT},
  {(sexp)"File-Descriptor", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, SEXP_FINALIZE_FILENON, SEXP_FILENO, 0, 0, 0, 0, 0, sexp_sizeof(fileno), 0, 0, 0, 0, 0, 0, 0, 0, SEXP_FINALIZE_FILENO},
  {(sexp)"Exception", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_EXCEPTION, sexp_offsetof(exception, kind), 6, 6, 0, 0, sexp_sizeof(exception), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Procedure", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_PROCEDURE, sexp_offsetof(procedure, bc), 2, 2, 0, 0, sexp_sizeof(procedure), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Macro", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_MACRO, sexp_offsetof(macro, proc), 4, 4, 0, 0, sexp_sizeof(macro), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Sc", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_SYNCLO, sexp_offsetof(synclo, env), 4, 4, 0, 0, sexp_sizeof(synclo), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Environment", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_ENV, sexp_offsetof(env, parent), 3+(SEXP_USE_STABLE_ABI||SEXP_USE_RENAME_BINDINGS), 3+(SEXP_USE_STABLE_ABI||SEXP_USE_RENAME_BINDINGS), 0, 0, sexp_sizeof(env), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Bytecode", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_BYTECODE, sexp_offsetof(bytecode, name), 3, 3, 0, 0, sexp_sizeof(bytecode), offsetof(struct sexp_struct, value.bytecode.length), 1, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Core-Form", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_CORE, sexp_offsetof(core, name), 1, 1, 0, 0, sexp_sizeof(core), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
#if SEXP_USE_STABLE_ABI || SEXP_USE_DL
  {(sexp)"Dynamic-Library", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, SEXP_FINALIZE_DLN, SEXP_DL, sexp_offsetof(dl, file), 1, 1, 0, 0, sexp_sizeof(dl), 0, 0, 0, 0, 0, 0, 0, 0, SEXP_FINALIZE_DL},
#endif
  {(sexp)"Opcode", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_OPCODE, sexp_offsetof(opcode, name), 11, 11, 0, 0, sexp_sizeof(opcode), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Lambda", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_LAMBDA, sexp_offsetof(lambda, name), 11, 11, 0, 0, sexp_sizeof(lambda), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"If", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_CND, sexp_offsetof(cnd, test), 4, 4, 0, 0, sexp_sizeof(cnd), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Ref", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_REF, sexp_offsetof(ref, name), 3, 3, 0, 0, sexp_sizeof(ref), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Set!", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_SET, sexp_offsetof(set, var), 3, 3, 0, 0, sexp_sizeof(set), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Set-Syn!", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_SET_SYN, sexp_offsetof(set_syn, var), 3, 3, 0, 0, sexp_sizeof(set_syn), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Seq", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_SEQ, sexp_offsetof(seq, ls), 2, 2, 0, 0, sexp_sizeof(seq), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Lit", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_simple_object, NULL, NULL, SEXP_LIT, sexp_offsetof(lit, value), 2, 2, 0, 0, sexp_sizeof(lit), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Stack", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_STACK, sexp_sizeof(stack), 0, 0, sexp_offsetof(stack, top), 1, sexp_sizeof(stack), offsetof(struct sexp_struct, value.stack.length), sizeof(sexp), 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Context", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_CONTEXT, sexp_offsetof(context, stack), 12+(SEXP_USE_STABLE_ABI||SEXP_USE_DL), 12+(SEXP_USE_STABLE_ABI||SEXP_USE_DL), 0, 0, sexp_sizeof(context), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
  {(sexp)"Cpointer", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_CPOINTER, sexp_offsetof(cpointer, parent), 1, 0, 0, 0, sexp_sizeof(cpointer), sexp_offsetof(cpointer, length), 1, 0, 0, 0, 0, 0, 0, NULL},
#if SEXP_USE_STABLE_ABI || SEXP_USE_UNIFORM_VECTOR_LITERALS
  {(sexp)"Uniform-Vector", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, (sexp)sexp_write_uvector, NULL, (sexp)"sexp_finalize_uvector", SEXP_UNIFORM_VECTOR, sexp_offsetof(uvector, bytes), 1, 1, 0, 0, sexp_sizeof(uvector), 0, 0, 0, 0, 0, 0, 0, 0, sexp_finalize_uvector},
#endif
#if SEXP_USE_STABLE_ABI || SEXP_USE_AUTO_FORCE
  {(sexp)"Promise", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_PROMISE, sexp_offsetof(promise, value), 1, 1, 0, 0, sexp_sizeof(promise), 0, 0, 0, 0, 0, 0, 0, 0, NULL},
#endif
#if SEXP_USE_STABLE_ABI || SEXP_USE_WEAK_REFERENCES
  {(sexp)"Ephemeron", SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, SEXP_FALSE, NULL, NULL, NULL, SEXP_EPHEMERON, sexp_offsetof(ephemeron, key), 2, 0, 0, 0, sexp_sizeof(ephemeron), 0, 0, sexp_offsetof(ephemeron, key), 1, 0, 0, 1, 0, NULL},
#endif
};

#define SEXP_INIT_NUM_TYPES (SEXP_NUM_CORE_TYPES*2)

#if SEXP_USE_TYPE_DEFS

sexp sexp_register_type_op (sexp ctx, sexp self, sexp_sint_t n, sexp name,
                            sexp parent, sexp slots,
                            sexp fb, sexp felb, sexp flb, sexp flo, sexp fls,
                            sexp sb, sexp so, sexp sc, sexp w, sexp wb, sexp wo,
                            sexp ws, sexp we, sexp p, const char* fname, sexp_proc2 f) {
  sexp *v1, *v2;
  sexp_gc_var2(res, type);
  sexp_uint_t i, len, num_types=sexp_context_num_types(ctx),
    type_array_size=sexp_context_type_array_size(ctx);
  sexp_gc_preserve2(ctx, res, type);
  if (num_types >= SEXP_MAXIMUM_TYPES) {
    res = sexp_user_exception(ctx, self, "register-type: exceeded maximum type limit", name);
  } else if (! sexp_stringp(name)) {
    res = sexp_type_exception(ctx, self, SEXP_STRING, name);
  } else {
    if (num_types >= type_array_size) {
      len = type_array_size*2;
      if (len > SEXP_MAXIMUM_TYPES) len = SEXP_MAXIMUM_TYPES;
      res = sexp_make_vector(ctx, sexp_make_fixnum(len), SEXP_VOID);
      if (sexp_exceptionp(res)) {
        sexp_gc_release2(ctx);
        return res;
      }
      v1 = sexp_vector_data(res);
      v2 = sexp_vector_data(sexp_global(ctx, SEXP_G_TYPES));
      for (i=0; i<num_types; i++)
        v1[i] = v2[i];
      sexp_global(ctx, SEXP_G_TYPES) = res;
    }
    sexp_type_by_index(ctx, num_types) = sexp_alloc_type(ctx, type, SEXP_TYPE);
    type = sexp_type_by_index(ctx, num_types);
    if (!sexp_exceptionp(type)) {
      sexp_pointer_tag(type) = SEXP_TYPE;
      sexp_type_tag(type) = num_types;
      sexp_type_slots(type) = slots;
      sexp_type_field_base(type) = (short)sexp_unbox_fixnum(fb);
      sexp_type_field_eq_len_base(type) = (short)sexp_unbox_fixnum(felb);
      sexp_type_field_len_base(type) = (short)sexp_unbox_fixnum(flb);
      sexp_type_field_len_off(type) = (short)sexp_unbox_fixnum(flo);
      sexp_type_field_len_scale(type) = (unsigned short)sexp_unbox_fixnum(fls);
      sexp_type_size_base(type) = (short)sexp_unbox_fixnum(sb);
      sexp_type_size_off(type) = (short)sexp_unbox_fixnum(so);
      sexp_type_size_scale(type) = (unsigned short)sexp_unbox_fixnum(sc);
      sexp_type_weak_base(type) = (short)sexp_unbox_fixnum(w);
      sexp_type_weak_len_base(type) = (short)sexp_unbox_fixnum(wb);
      sexp_type_weak_len_off(type) = (short)sexp_unbox_fixnum(wo);
      sexp_type_weak_len_scale(type) = (short)sexp_unbox_fixnum(ws);
      sexp_type_weak_len_extra(type) = (short)sexp_unbox_fixnum(we);
      sexp_type_name(type) = name;
      sexp_type_getters(type) = SEXP_FALSE;
      sexp_type_setters(type) = SEXP_FALSE;
      sexp_type_finalize(type) = f;
      sexp_type_finalize_name(type) = (fname) ? sexp_c_string(ctx, fname, -1) : NULL;
      sexp_type_id(type) = SEXP_FALSE;
#if SEXP_USE_DL
      if (f) sexp_type_dl(type) = sexp_context_dl(ctx);
#endif
      sexp_type_print(type) = p;
      if (parent && sexp_typep(parent)) {
        len = sexp_vectorp(sexp_type_cpl(parent)) ? sexp_vector_length(sexp_type_cpl(parent)) : 1;
        sexp_type_cpl(type) = sexp_make_vector(ctx, sexp_make_fixnum(len+1), SEXP_VOID);
        if (parent && sexp_vectorp(sexp_type_cpl(parent)))
          memcpy(sexp_vector_data(sexp_type_cpl(type)),
                 sexp_vector_data(sexp_type_cpl(parent)),
                 len * sizeof(sexp));
        else
          sexp_vector_data(sexp_type_cpl(type))[len-1] = parent;
      } else {
        len = 0;
        sexp_type_cpl(type) = sexp_make_vector(ctx, SEXP_ONE, SEXP_VOID);
      }
      sexp_vector_data(sexp_type_cpl(type))[len] = type;
      sexp_type_depth(type) = (short)len;
      sexp_global(ctx, SEXP_G_NUM_TYPES) = sexp_make_fixnum(num_types + 1);
    }
    res = type;
  }
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_register_simple_type_op (sexp ctx, sexp self, sexp_sint_t n, sexp name, sexp parent, sexp slots) {
  short i, num_slots = (short)sexp_unbox_fixnum(sexp_length(ctx, slots));
  sexp type_size, num_slots_obj, cpl, tmp;
  if (parent && sexp_typep(parent)) {
    num_slots += (short)sexp_unbox_fixnum(sexp_length(ctx, sexp_type_slots(parent)));
    if (sexp_vectorp((cpl = sexp_type_cpl(parent))))
      for (i=(short)sexp_vector_length(cpl)-1; i>=0; i--) {
        tmp = sexp_vector_ref(cpl, sexp_make_fixnum(i));
        num_slots += (short)sexp_unbox_fixnum(sexp_length(ctx, sexp_type_slots(tmp)));
      }
  }
  num_slots_obj = sexp_make_fixnum(num_slots);
  type_size = sexp_make_fixnum(sexp_sizeof_header + sizeof(sexp)*num_slots);
  return
    sexp_register_type(ctx, name, parent, slots,
                       sexp_make_fixnum(sexp_offsetof_slot0),
                       num_slots_obj, num_slots_obj, SEXP_ZERO, SEXP_ZERO,
                       type_size, SEXP_ZERO, SEXP_ZERO, SEXP_ZERO, SEXP_ZERO,
                       SEXP_ZERO, SEXP_ZERO, SEXP_ZERO,
                       sexp_type_print(sexp_type_by_index(ctx, SEXP_EXCEPTION)),
                       NULL, NULL);
}

#if SEXP_USE_OBJECT_BRACE_LITERALS
sexp sexp_lookup_type_op(sexp ctx, sexp self, sexp_sint_t n, sexp name, sexp id) {
  int i;
  sexp res;
  const char* str;
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, name);
  str = sexp_string_data(name);
  if (sexp_fixnump(id)) {
    i = sexp_unbox_fixnum(id);
    if (i < sexp_context_num_types(ctx)
        && strcmp(str, sexp_string_data(sexp_type_name_by_index(ctx, i))) == 0)
      return sexp_type_by_index(ctx, i);
    else
      return SEXP_FALSE;
  }
  for (i=sexp_context_num_types(ctx)-1; i>=0; i--)
    if (strcmp(str, sexp_string_data(sexp_type_name_by_index(ctx, i))) == 0) {
      res = sexp_type_by_index(ctx, i);
      if (sexp_stringp(id)
          && !(sexp_stringp(sexp_type_id(res))
               && strcmp(sexp_string_data(id), sexp_string_data(sexp_type_id(res))) == 0))
        continue;
      return res;
    }
  return SEXP_FALSE;
}
#endif

sexp sexp_finalize_c_type (sexp ctx, sexp self, sexp_sint_t n, sexp obj) {
  if (sexp_cpointer_freep(obj))
    free(sexp_cpointer_value(obj));
  return SEXP_VOID;
}

#else
#define sexp_num_types SEXP_NUM_CORE_TYPES
#endif

/****************************** contexts ******************************/

static const char* sexp_initial_features[] = {
#ifdef sexp_architecture
  sexp_architecture,
#endif
#ifdef sexp_platform
  sexp_platform,
#endif
#if SEXP_BSD
  "bsd",
#endif
#if SEXP_DARWIN
  "darwin",
#endif
#if SEXP_OPENBSD
  "openbsd",
#endif
#if SEXP_FREEBSD
  "freebsd",
#endif
#if SEXP_NETBSD
  "netbsd",
#endif
#if SEXP_DRAGONFLY
  "dragonfly",
#endif
#if defined(_WIN32)
  "windows",
#endif
#if SEXP_USE_DL
  "dynamic-loading",
#endif
#if SEXP_USE_BIDIRECTIONAL_PORTS
  "bidir-ports",
#endif
#if SEXP_USE_MODULES
  "modules",
#endif
#if SEXP_USE_BOEHM
  "boehm-gc",
#endif
#if SEXP_USE_UTF8_STRINGS
  "full-unicode",
#endif
#if SEXP_USE_STRING_INDEX_TABLE
  "string-index",
#endif
#if SEXP_USE_STRING_REF_CACHE
  "string-ref-cache",
#endif
#if SEXP_USE_GREEN_THREADS
  "threads",
#endif
#if SEXP_USE_NTP_GETTIME
  "ntp",
#endif
#if SEXP_USE_AUTO_FORCE
  "auto-force",
#endif
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
  "uvector",
#endif
#if SEXP_USE_MINI_FLOAT_UNIFORM_VECTORS
  "mini-float",
#endif
#if SEXP_USE_COMPLEX
  "complex",
#endif
#if SEXP_USE_RATIOS
  "ratios",
#endif
  "r7rs",
  "chibi-" sexp_version,
  "chibi",
  NULL,
};

void sexp_init_context_globals (sexp ctx) {
  const char** features;
  int i, endianess_check = 1;
  sexp type, *vec, print=NULL;
  sexp_gc_var1(feature);
  sexp_context_globals(ctx)
    = sexp_make_vector(ctx, sexp_make_fixnum(SEXP_G_NUM_GLOBALS), SEXP_VOID);
#if ! SEXP_USE_GLOBAL_SYMBOLS
  sexp_global(ctx, SEXP_G_SYMBOLS) = sexp_make_vector(ctx, sexp_make_fixnum(SEXP_SYMBOL_TABLE_SIZE), SEXP_NULL);
#endif
  sexp_global(ctx, SEXP_G_STRICT_P) = SEXP_FALSE;
  sexp_global(ctx, SEXP_G_NO_TAIL_CALLS_P) = SEXP_FALSE;
#if SEXP_USE_FOLD_CASE_SYMS
  sexp_global(ctx, SEXP_G_FOLD_CASE_P) = sexp_make_boolean(SEXP_DEFAULT_FOLD_CASE_SYMS);
#endif
#if ! SEXP_USE_BOEHM
  sexp_global(ctx, SEXP_G_PRESERVATIVES) = SEXP_NULL;
#endif
#if SEXP_USE_WEAK_REFERENCES
  sexp_global(ctx, SEXP_G_WEAK_OBJECTS_PRESENT) = SEXP_FALSE;
  sexp_global(ctx, SEXP_G_FILE_DESCRIPTORS) = SEXP_FALSE;
  sexp_global(ctx, SEXP_G_NUM_FILE_DESCRIPTORS) = SEXP_ZERO;
#endif
  sexp_global(ctx, SEXP_G_OOM_ERROR) = sexp_user_exception(ctx, SEXP_FALSE, "out of memory", SEXP_NULL);
  sexp_global(ctx, SEXP_G_OOS_ERROR) = sexp_user_exception(ctx, SEXP_FALSE, "out of stack space", SEXP_NULL);
  sexp_global(ctx, SEXP_G_ABI_ERROR) = sexp_user_exception(ctx, SEXP_FALSE, "incompatible ABI", SEXP_NULL);
  sexp_global(ctx, SEXP_G_INTERRUPT_ERROR) = sexp_user_exception(ctx, SEXP_FALSE, "interrupt", SEXP_NULL);
  sexp_global(ctx, SEXP_G_QUOTE_SYMBOL) = sexp_intern(ctx, "quote", -1);
  sexp_global(ctx, SEXP_G_QUASIQUOTE_SYMBOL) = sexp_intern(ctx, "quasiquote", -1);
  sexp_global(ctx, SEXP_G_UNQUOTE_SYMBOL) = sexp_intern(ctx, "unquote", -1);
  sexp_global(ctx, SEXP_G_UNQUOTE_SPLICING_SYMBOL) = sexp_intern(ctx, "unquote-splicing", -1);
  sexp_global(ctx, SEXP_G_SYNTAX_SYMBOL) = sexp_intern(ctx, "syntax", -1);
  sexp_global(ctx, SEXP_G_QUASISYNTAX_SYMBOL) = sexp_intern(ctx, "quasisyntax", -1);
  sexp_global(ctx, SEXP_G_UNSYNTAX_SYMBOL) = sexp_intern(ctx, "unsyntax", -1);
  sexp_global(ctx, SEXP_G_UNSYNTAX_SPLICING_SYMBOL) = sexp_intern(ctx, "unsyntax-splicing", -1);
  sexp_global(ctx, SEXP_G_CUR_IN_SYMBOL) = sexp_intern(ctx, "current-input-port", -1);
  sexp_global(ctx, SEXP_G_CUR_OUT_SYMBOL) = sexp_intern(ctx, "current-output-port", -1);
  sexp_global(ctx, SEXP_G_CUR_ERR_SYMBOL) = sexp_intern(ctx, "current-error-port", -1);
  sexp_global(ctx, SEXP_G_INTERACTION_ENV_SYMBOL) = sexp_intern(ctx, "interaction-environment", -1);
  sexp_global(ctx, SEXP_G_CONTINUABLE_SYMBOL) = sexp_intern(ctx, "continuable", -1);
  sexp_global(ctx, SEXP_G_EMPTY_VECTOR) = sexp_alloc_type(ctx, vector, SEXP_VECTOR);
  sexp_vector_length(sexp_global(ctx, SEXP_G_EMPTY_VECTOR)) = 0;
  sexp_global(ctx, SEXP_G_FEATURES) = SEXP_NULL;
  sexp_push(ctx, sexp_global(ctx, SEXP_G_FEATURES), SEXP_FALSE);
  sexp_car(sexp_global(ctx, SEXP_G_FEATURES)) = sexp_intern(ctx, (*(unsigned char*) &endianess_check) ? "little-endian" : "big-endian", -1);
  sexp_global(ctx, SEXP_G_ENDIANNESS) = sexp_intern(ctx, (*(unsigned char*) &endianess_check) ? "little" : "big", -1);
  sexp_gc_preserve1(ctx, feature);
  for (features=sexp_initial_features; *features; features++) {
    feature = sexp_intern(ctx, *features, -1);
    if (sexp_not(sexp_memq(ctx, feature, sexp_global(ctx, SEXP_G_FEATURES)))) {
      sexp_push(ctx, sexp_global(ctx, SEXP_G_FEATURES), SEXP_FALSE);
      sexp_car(sexp_global(ctx, SEXP_G_FEATURES)) = feature;
    }
  }
  sexp_gc_release1(ctx);
  sexp_global(ctx, SEXP_G_NUM_TYPES) = sexp_make_fixnum(SEXP_NUM_CORE_TYPES);
  sexp_global(ctx, SEXP_G_TYPES)
    = sexp_make_vector(ctx, sexp_make_fixnum(SEXP_INIT_NUM_TYPES), SEXP_VOID);
  vec = sexp_vector_data(sexp_global(ctx, SEXP_G_TYPES));
  for (i=0; i<SEXP_NUM_CORE_TYPES; i++) {
    type = sexp_alloc_type(ctx, type, SEXP_TYPE);
    if (!type) {
      return; /* TODO - fundamental OOM, what to do here? */
    }
    memcpy(&(type->value), &(_sexp_type_specs[i]), sizeof(_sexp_type_specs[0]));
    vec[i] = type;
    sexp_type_name(type) = sexp_c_string(ctx, (char*)sexp_type_name(type), -1);
    if (sexp_type_finalize_name(type)) {
      sexp_type_finalize_name(type) = sexp_c_string(ctx, (char*)sexp_type_finalize_name(type), -1);
    }
    if (sexp_type_print(type)) {
      if (print && ((sexp_proc1)sexp_type_print(type) == sexp_opcode_func(print)))
        sexp_type_print(type) = print;
      else
        sexp_type_print(type) = print = sexp_make_foreign(ctx, "sexp_write_simple_object", 3, 0, NULL, (sexp_proc1)sexp_type_print(type), NULL);
    }
  }
}

#if ! SEXP_USE_GLOBAL_HEAP
sexp sexp_bootstrap_context (sexp_uint_t size, sexp_uint_t max_size) {
  sexp ctx;
  sexp_heap heap;
  struct sexp_struct dummy_ctx;
  if (size < SEXP_MINIMUM_HEAP_SIZE) size = SEXP_INITIAL_HEAP_SIZE;
  size = sexp_heap_align(size);
  max_size = sexp_heap_align(max_size);
  heap = sexp_make_heap(size, max_size, 0);
  if (!heap) return 0;
  sexp_pointer_tag(&dummy_ctx) = SEXP_CONTEXT;
  sexp_context_mark_stack_ptr(&dummy_ctx) = NULL;
  sexp_context_saves(&dummy_ctx) = NULL;
  sexp_context_heap(&dummy_ctx) = heap;
  ctx = sexp_alloc_type(&dummy_ctx, context, SEXP_CONTEXT);
  if (!ctx || sexp_exceptionp(ctx)) {
    sexp_free_heap(heap);
  } else {
    sexp_context_heap(ctx) = heap;
#if SEXP_USE_FIXED_CHUNK_SIZE_HEAPS
    heap->chunk_size = sexp_heap_align(1);
    heap->next = sexp_make_heap(size, max_size, 0);
    if (heap->next) {
      heap->next->chunk_size = sexp_heap_align(1 + sexp_heap_align(1));
      heap->next->next = sexp_make_heap(size, max_size, 0);
      if (heap->next->next) {
        heap->next->next->chunk_size = sexp_heap_align(1 + sexp_heap_align(1 + sexp_heap_align(1)));
        heap->next->next->next = sexp_make_heap(size, max_size, 0);
      }
    }
#endif
  }
  return ctx;
}
#endif

sexp sexp_make_context (sexp ctx, size_t size, size_t max_size) {
  sexp_gc_var1(res);
  if (ctx) sexp_gc_preserve1(ctx, res);
#if ! SEXP_USE_GLOBAL_HEAP
  if (! ctx) {
    res = sexp_bootstrap_context(size, max_size);
    if (!res || sexp_exceptionp(res)) return res;
  } else
#endif
    {
      res = sexp_alloc_type(ctx, context, SEXP_CONTEXT);
#if ! SEXP_USE_BOEHM && ! SEXP_USE_MALLOC
      sexp_context_heap(res) = sexp_context_heap(ctx);
#endif
    }
  if (!res || sexp_exceptionp(res)) return res;
  sexp_context_parent(res) = ctx;
  sexp_context_name(res) = sexp_context_specific(res) = SEXP_FALSE;
  sexp_context_mark_stack_ptr(res) = NULL;
  sexp_context_saves(res) = NULL;
  sexp_context_params(res) = SEXP_NULL;
  sexp_context_last_fp(res) = 0;
#if SEXP_USE_TIME_GC
  sexp_context_gc_count(res) = 0;
  sexp_context_gc_usecs(res) = 0;
#endif
#if SEXP_USE_TRACK_ALLOC_TIMES
  sexp_context_alloc_count(res) = 0;
  sexp_context_alloc_usecs(res) = 0;
  sexp_context_alloc_usecs_sq(res) = 0;
#endif
  sexp_context_tracep(res) = 0;
  sexp_context_timeoutp(res) = 0;
  sexp_context_tailp(res) = 1;
#if SEXP_USE_GREEN_THREADS
  sexp_context_errorp(res) = 0;
  sexp_context_event(res) = SEXP_FALSE;
  sexp_context_refuel(res) = SEXP_DEFAULT_QUANTUM;
#endif
#if SEXP_USE_DL
  sexp_context_dl(res) = ctx ? sexp_context_dl(ctx) : SEXP_FALSE;
#endif
  if (ctx) {
    sexp_context_globals(res) = sexp_context_globals(ctx);
    sexp_context_dk(res) = sexp_context_dk(ctx);
    sexp_gc_release1(ctx);
  } else {
    sexp_init_context_globals(res);
  }
  return res;
}

#if ! SEXP_USE_GLOBAL_HEAP
sexp sexp_destroy_context (sexp ctx) {
  sexp_heap heap, tmp;
  size_t sum_freed;
  if (sexp_context_heap(ctx)) {
    heap = sexp_context_heap(ctx);
#if SEXP_USE_DEBUG_GC
    sexp_debug_heap_stats(heap);
#endif
#if SEXP_USE_TRACK_ALLOC_TIMES
    sexp_debug_alloc_times(ctx);
#endif
#if SEXP_USE_TRACK_ALLOC_SIZES
    sexp_debug_alloc_sizes(ctx);
#endif
    sexp_markedp(ctx) = 1;
    sexp_markedp(sexp_context_globals(ctx)) = 1;
    sexp_mark(ctx, sexp_global(ctx, SEXP_G_TYPES));
    if (sexp_finalize(ctx) == SEXP_FALSE) { return SEXP_FALSE; }
    sexp_sweep(ctx, &sum_freed);
    if (sexp_finalize(ctx) == SEXP_FALSE) { return SEXP_FALSE; }
    sexp_context_heap(ctx) = NULL;
    for ( ; heap; heap=tmp) {
      tmp = heap->next;
      sexp_free_heap(heap);
    }
  }
  return SEXP_TRUE;
}
#endif

/***************************** exceptions *****************************/

sexp sexp_make_exception (sexp ctx, sexp kind, sexp message, sexp irritants,
                          sexp procedure, sexp source) {
  sexp exn = sexp_alloc_type(ctx, exception, SEXP_EXCEPTION);
  sexp_exception_kind(exn) = kind;
  sexp_exception_message(exn) = message;
  sexp_exception_irritants(exn) = irritants;
  sexp_exception_procedure(exn) = procedure;
  sexp_exception_source(exn) = source;
  sexp_exception_stack_trace(exn) = SEXP_FALSE;
  return exn;
}

sexp sexp_string_cat3 (sexp ctx, const char *pre, const char *mid, const char* suf) {
  int plen=strlen(pre), mlen=strlen(mid), slen=strlen(suf);
  char *s;
  sexp str;
  str = sexp_make_string(ctx, sexp_make_fixnum(plen+mlen+slen), SEXP_VOID);
  memcpy(s=sexp_string_data(str), pre, plen);
  memcpy(s+plen, mid, mlen);
  memcpy(s+plen+mlen, suf, slen);
  return str;
}

sexp sexp_user_exception (sexp ctx, sexp self, const char *ms, sexp ir) {
  sexp res;
  sexp_gc_var3(sym, str, irr);
  sexp_gc_preserve3(ctx, sym, str, irr);
  res = sexp_make_exception(ctx, sym = sexp_intern(ctx, "user", -1),
                            str = sexp_c_string(ctx, ms, -1),
                            ((sexp_pairp(ir) || sexp_nullp(ir))
                             ? ir : (irr = sexp_list1(ctx, ir))),
                            self, SEXP_FALSE);
  sexp_gc_release3(ctx);
  return res;
}

sexp sexp_user_exception_ls (sexp ctx, sexp self, const char *msg, int n, ...) {
  int i;
  va_list ap;
  sexp_gc_var2(res, ir);
  sexp_gc_preserve2(ctx, res, ir);
  va_start(ap, n);
  for (i=0, ir=SEXP_NULL; i < n; ++i) {
    ir = sexp_cons(ctx, va_arg(ap, sexp), ir);
  }
  ir = sexp_nreverse(ctx, ir);
  res = sexp_user_exception(ctx, self, msg, ir);
  sexp_gc_release2(ctx);
  va_end(ap);
  return res;
}

sexp sexp_file_exception (sexp ctx, sexp self, const char *ms, sexp ir) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_user_exception(ctx, self, ms, ir);
  sexp_exception_kind(res) = sexp_intern(ctx, "file", -1);
  sexp_gc_release1(ctx);
  return res;
}

static sexp type_exception (sexp ctx, sexp self, sexp str, sexp obj, sexp src) {
  sexp_gc_var2(res, sym);
  sexp_gc_preserve2(ctx, res, sym);
  sym = sexp_intern(ctx, "type", -1);
  res = sexp_make_exception(ctx, sym, str, obj, self, src);
  sexp_exception_irritants(res)=sexp_list1(ctx, sexp_exception_irritants(res));
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_xtype_exception (sexp ctx, sexp self, const char *msg, sexp obj) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_c_string(ctx, msg, -1);
  res = type_exception(ctx, self, res, obj, SEXP_FALSE);
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_type_exception (sexp ctx, sexp self, sexp_uint_t type_id, sexp obj) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_string_cat3(ctx, "invalid type, expected ",
                         sexp_string_data(sexp_type_name_by_index(ctx, type_id)), "");
  res = type_exception(ctx, self, res, obj, SEXP_FALSE);
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_range_exception (sexp ctx, sexp obj, sexp start, sexp end) {
  sexp_gc_var2(res, msg);
  sexp_gc_preserve2(ctx, res, msg);
  msg = sexp_c_string(ctx, "bad index range", -1);
  res = sexp_list2(ctx, start, end);
  res = sexp_cons(ctx, obj, res);
  res = sexp_make_exception(ctx, sexp_intern(ctx, "range", -1), msg, res,
                            SEXP_FALSE, SEXP_FALSE);
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_print_exception_op (sexp ctx, sexp self, sexp_sint_t n, sexp exn, sexp out) {
  sexp_gc_var2(ls, tmp);
  /* unwrap continuable exceptions */
  if (sexp_exceptionp(exn)
      && ((sexp_exception_kind(exn) == sexp_global(ctx, SEXP_G_CONTINUABLE_SYMBOL)
           && sexp_exceptionp(sexp_exception_irritants(exn)))
          || sexp_exception_kind(exn) == SEXP_UNCAUGHT)) {
    return sexp_print_exception_op(ctx, self, n, sexp_exception_irritants(exn), out);
  }
  sexp_gc_preserve2(ctx, ls, tmp);
  if (! sexp_oportp(out))
    out = tmp = sexp_make_output_port(ctx, stderr, SEXP_FALSE);
  sexp_write_string(ctx, "ERROR", out);
  if (sexp_exceptionp(exn)) {
    if (sexp_exception_procedure(exn)) {
      if (sexp_procedurep(sexp_exception_procedure(exn))) {
        ls = sexp_bytecode_name(
              sexp_procedure_code(sexp_exception_procedure(exn)));
        if (ls && sexp_symbolp(ls)) {
          sexp_write_string(ctx, " in ", out);
          sexp_write(ctx, ls, out);
        }
      } else if (sexp_opcodep(sexp_exception_procedure(exn))) {
        sexp_write_string(ctx, " in ", out);
        sexp_write(ctx, sexp_opcode_name(sexp_exception_procedure(exn)), out);
      }
    }
    ls = sexp_exception_source(exn);
    if ((! (ls && sexp_pairp(ls)))
        && sexp_exception_procedure(exn)
        && sexp_procedurep(sexp_exception_procedure(exn)))
      ls = sexp_bytecode_source(sexp_procedure_code(sexp_exception_procedure(exn)));
    if (ls && sexp_pairp(ls)) {
      if (sexp_fixnump(sexp_cdr(ls)) && (sexp_cdr(ls) >= SEXP_ZERO)) {
        sexp_write_string(ctx, " on line ", out);
        sexp_write(ctx, sexp_cdr(ls), out);
      }
      if (sexp_stringp(sexp_car(ls))) {
        sexp_write_string(ctx, " of file ", out);
        sexp_write_string(ctx, sexp_string_data(sexp_car(ls)), out);
      }
    }
    sexp_write_string(ctx, ": ", out);
    if (sexp_stringp(sexp_exception_message(exn)))
      sexp_write_string(ctx, sexp_string_data(sexp_exception_message(exn)), out);
    else
      sexp_write(ctx, sexp_exception_message(exn), out);
    if (sexp_exception_irritants(exn)
        && sexp_pairp(sexp_exception_irritants(exn))) {
      if (sexp_nullp(sexp_cdr(sexp_exception_irritants(exn)))) {
        sexp_write_string(ctx, ": ", out);
        sexp_write(ctx, sexp_car(sexp_exception_irritants(exn)), out);
        sexp_write_string(ctx, "\n", out);
      } else {
        sexp_write_string(ctx, "\n", out);
        for (ls=sexp_exception_irritants(exn);
             sexp_pairp(ls); ls=sexp_cdr(ls)) {
          sexp_write_string(ctx, "    ", out);
          sexp_write(ctx, sexp_car(ls), out);
          sexp_write_char(ctx, '\n', out);
        }
      }
    } else {
      sexp_write_char(ctx, '\n', out);
    }
  } else {
    sexp_write_string(ctx, ": ", out);
    if (sexp_stringp(exn))
      sexp_write_string(ctx, sexp_string_data(exn), out);
    else
      sexp_write(ctx, exn, out);
    sexp_write_char(ctx, '\n', out);
  }
  sexp_gc_release2(ctx);
  return SEXP_VOID;
}

sexp sexp_read_error (sexp ctx, const char *msg, sexp ir, sexp port) {
  sexp res;
  sexp_gc_var4(sym, name, str, irr);
  sexp_gc_preserve4(ctx, sym, name, str, irr);
  name = (sexp_port_name(port) ? sexp_port_name(port) : SEXP_FALSE);
  name = sexp_cons(ctx, name, sexp_make_fixnum(sexp_port_line(port)));
  str = sexp_c_string(ctx, msg, -1);
  irr = ((sexp_pairp(ir) || sexp_nullp(ir)) ? ir : sexp_list1(ctx, ir));
  res = sexp_make_exception(ctx, sym = sexp_intern(ctx, "read", -1),
                            str, irr, SEXP_FALSE, name);
  sexp_gc_release4(ctx);
  return res;
}

sexp sexp_read_incomplete_error (sexp ctx, const char *msg, sexp ir, sexp port) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_read_error(ctx, msg, ir, port);
  if (sexp_exceptionp(res))
    sexp_exception_kind(res) = sexp_intern(ctx, "read-incomplete", -1);
  sexp_gc_release1(ctx);
  return res;
}

/*************************** list utilities ***************************/

sexp sexp_cons_op (sexp ctx, sexp self, sexp_sint_t n, sexp head, sexp tail) {
  sexp pair = sexp_alloc_type(ctx, pair, SEXP_PAIR);
  if (sexp_exceptionp(pair)) return pair;
  sexp_car(pair) = head;
  sexp_cdr(pair) = tail;
  sexp_pair_source(pair) = SEXP_FALSE;
  return pair;
}

sexp sexp_list2 (sexp ctx, sexp a, sexp b) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_cons(ctx, b, SEXP_NULL);
  res = sexp_cons(ctx, a, res);
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_list3 (sexp ctx, sexp a, sexp b, sexp c) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_list2(ctx, b, c);
  res = sexp_cons(ctx, a, res);
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_listp_op (sexp ctx, sexp self, sexp_sint_t n, sexp hare) {
  sexp turtle;
  if (! sexp_pairp(hare))
    return sexp_make_boolean(sexp_nullp(hare));
  turtle = hare;
  hare = sexp_cdr(hare);
  for ( ; sexp_pairp(hare); turtle=sexp_cdr(turtle)) {
    if (hare == turtle) return SEXP_FALSE;
    hare = sexp_cdr(hare);
    if (sexp_pairp(hare)) hare = sexp_cdr(hare);
  }
  return sexp_make_boolean(sexp_nullp(hare));
}

sexp sexp_memq_op (sexp ctx, sexp self, sexp_sint_t n, sexp x, sexp ls) {
  while (sexp_pairp(ls))
    if (x == sexp_car(ls))
      return ls;
    else
      ls = sexp_cdr(ls);
  return SEXP_FALSE;
}

sexp sexp_assq_op (sexp ctx, sexp self, sexp_sint_t n, sexp x, sexp ls) {
  while (sexp_pairp(ls))
    if (sexp_pairp(sexp_car(ls)) && (x == sexp_caar(ls)))
      return sexp_car(ls);
    else
      ls = sexp_cdr(ls);
  return SEXP_FALSE;
}

sexp sexp_reverse_op (sexp ctx, sexp self, sexp_sint_t n, sexp ls) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  for (res=SEXP_NULL; sexp_pairp(ls); ls=sexp_cdr(ls))
    res = sexp_cons(ctx, sexp_car(ls), res);
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_nreverse_op (sexp ctx, sexp self, sexp_sint_t n, sexp ls) {
  sexp a, b, tmp;
  if (ls == SEXP_NULL) return ls;
  sexp_assert_type(ctx, sexp_pairp, SEXP_PAIR, ls);
  b = ls;
  a = sexp_cdr(ls);
  sexp_cdr(b) = SEXP_NULL;
  for ( ; sexp_pairp(a); b=a, a=tmp) {
    tmp = sexp_cdr(a);
    sexp_cdr(a) = b;
  }
  return b;
}

sexp sexp_copy_list_op (sexp ctx, sexp self, sexp_sint_t n, sexp ls) {
  sexp tmp;
  sexp_gc_var1(res);
  if (! sexp_pairp(ls)) return ls;
  sexp_gc_preserve1(ctx, res);
  tmp = res = sexp_cons(ctx, sexp_car(ls), sexp_cdr(ls));
  for (ls=sexp_cdr(ls); sexp_pairp(ls); ls=sexp_cdr(ls), tmp=sexp_cdr(tmp))
    sexp_cdr(tmp) = sexp_cons(ctx, sexp_car(ls), sexp_cdr(ls));
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_append2_op (sexp ctx, sexp self, sexp_sint_t n, sexp a, sexp b) {
  sexp_gc_var2(a1, b1);
  sexp_gc_preserve2(ctx, a1, b1);
  b1 = b;
  for (a1=sexp_reverse(ctx, a); sexp_pairp(a1); a1=sexp_cdr(a1))
    b1 = sexp_cons(ctx, sexp_car(a1), b1);
  sexp_gc_release2(ctx);
  return b1;
}

sexp sexp_length_op (sexp ctx, sexp self, sexp_sint_t n, sexp ls1) {
  sexp ls2;
  sexp_uint_t res = 1;
  if (!sexp_pairp(ls1))
    return SEXP_ZERO;
  for (ls2=sexp_cdr(ls1); sexp_pairp(ls2) && sexp_pairp(sexp_cdr(ls2));
       res+=2, ls1=sexp_cdr(ls1), ls2=sexp_cddr(ls2))
    if (ls1 == ls2)
      return SEXP_FALSE;
  return sexp_make_fixnum(res + (sexp_pairp(ls2) ? 1 : 0));
}

sexp sexp_equalp_bound (sexp ctx, sexp self, sexp_sint_t n, sexp a, sexp b, sexp depth, sexp bound) {
  sexp_uint_t left_size, right_size;
  sexp_sint_t i, len;
  sexp t, *p, *q, depth2;
  char *p_left, *p_right, *q_left, *q_right;

 loop:
  if (a == b)
    return bound;
  else if ((!a || !sexp_pointerp(a)) || (!b || !sexp_pointerp(b))
           || (sexp_pointer_tag(a) != sexp_pointer_tag(b)))
    return SEXP_FALSE;

  /* a and b are both pointers of the same type */
#if SEXP_USE_BIGNUMS
  if (sexp_pointer_tag(a) == SEXP_BIGNUM)
    return !sexp_bignum_compare(a, b) ? bound : SEXP_FALSE;
#endif
#if SEXP_USE_FLONUMS && ! SEXP_USE_IMMEDIATE_FLONUMS
  if (sexp_pointer_tag(a) == SEXP_FLONUM)
    return sexp_flonum_eqv(a, b) ? bound : SEXP_FALSE;
#endif
  /* check limits */
  if (sexp_unbox_fixnum(bound) < 0 || sexp_unbox_fixnum(depth) < 0)
    return bound;
  depth2 = sexp_fx_sub(depth, SEXP_ONE);
  bound = sexp_fx_sub(bound, SEXP_ONE);
  t = sexp_object_type(ctx, a);
  p_left = ((char*)a) + offsetof(struct sexp_struct, value);
  p = (sexp*) (((char*)a) + sexp_type_field_base(t));
  q_left = ((char*)b) + offsetof(struct sexp_struct, value);
  q = (sexp*) (((char*)b) + sexp_type_field_base(t));
  /* if no fields, the base is value (just past the header) */
  if ((sexp)p == a) {p=(sexp*)p_left; q=(sexp*)q_left;}
  /* check preliminary non-object data */
  left_size = (char*)p - p_left;
  if ((left_size > 0) && memcmp(p_left, q_left, left_size))
    return SEXP_FALSE;
  /* check trailing non-object data */
  p_right = ((char*)p + sexp_type_num_slots_of_object(t,a)*sizeof(sexp));
  right_size = ((char*)a + sexp_type_size_of_object(t, a)) - p_right;
  if (right_size > 0) {
    q_right = ((char*)q + sexp_type_num_slots_of_object(t,b)*sizeof(sexp));
    if (right_size != ((char*)b + sexp_type_size_of_object(t, b)) - q_right)
      return SEXP_FALSE;
    if (memcmp(p_right, q_right, right_size))
      return SEXP_FALSE;
  }
  /* left and right non-object data is the same, now check eq-object slots */
  len = sexp_type_num_eq_slots_of_object(t, a);
  if (len > 0) {
    for (; len > 1; len--) {
      a = p[len-1]; b = q[len-1];
      if (a != b) {
        if ((!a || !sexp_pointerp(a)) || (!b || !sexp_pointerp(b))
            || (sexp_pointer_tag(a) != sexp_pointer_tag(b)))
          return SEXP_FALSE;
        else break;
      }
    }
    for (i=0; i<len-1; i++) {
      bound = sexp_equalp_bound(ctx, self, n, p[i], q[i], depth2, bound);
      if (sexp_not(bound)) return SEXP_FALSE;
    }
    /* tail-recurse on the last value (same depth) */
    a = p[len-1]; b = q[len-1]; goto loop;
  }
  return bound;
}

sexp sexp_equalp_op (sexp ctx, sexp self, sexp_sint_t n, sexp a, sexp b) {
  return sexp_make_boolean(
    sexp_truep(sexp_equalp_bound(ctx, self, n, a, b,
                                 sexp_make_fixnum(SEXP_DEFAULT_EQUAL_DEPTH),
                                 sexp_make_fixnum(SEXP_DEFAULT_EQUAL_BOUND))));
}

/********************* strings, symbols, vectors **********************/

sexp sexp_flonump_op (sexp ctx, sexp self, sexp_sint_t n, sexp x) {
  return sexp_make_boolean(sexp_flonump(x));
}

#if ! SEXP_USE_IMMEDIATE_FLONUMS
sexp sexp_make_flonum (sexp ctx, double f) {
  sexp x = sexp_alloc_type(ctx, flonum, SEXP_FLONUM);
  if (sexp_exceptionp(x)) return x;
  sexp_flonum_value(x) = f;
  return x;
}
#else
#if SEXP_64_BIT
float sexp_flonum_value (sexp x) {
  union sexp_flonum_conv r;
  r.bits = (sexp_uint_t)x >> 32;
  return r.flonum;
}
sexp sexp_make_flonum (sexp ctx, float f) {
  union sexp_flonum_conv x;
  x.flonum = f;
  return (sexp)(((sexp_uint_t)(x.bits) << 32) + SEXP_IFLONUM_TAG);
}
#endif
#endif

sexp sexp_make_bytes_op (sexp ctx, sexp self, sexp_sint_t n, sexp len, sexp i) {
  sexp_sint_t clen = sexp_unbox_fixnum(len);
  sexp s;
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, len);
  if (clen < 0) return sexp_xtype_exception(ctx, self, "negative length", len);
  s = sexp_alloc_atomic(ctx, sexp_sizeof(bytes)+clen+1);
  if (sexp_exceptionp(s)) return s;
  sexp_pointer_tag(s) = SEXP_BYTES;
#if SEXP_USE_HEADER_MAGIC
  sexp_pointer_magic(s) = SEXP_POINTER_MAGIC;
#endif
  sexp_bytes_length(s) = clen;
  if (sexp_fixnump(i))
    memset(sexp_bytes_data(s), sexp_unbox_fixnum(i), clen);
  sexp_bytes_data(s)[clen] = '\0';
  return s;
}

#if SEXP_USE_UNIFORM_VECTOR_LITERALS
sexp sexp_make_uvector_op(sexp ctx, sexp self, sexp_sint_t n, sexp elt_type, sexp len) {
  sexp_sint_t etype = sexp_unbox_fixnum(elt_type), elen = sexp_unbox_fixnum(len), clen;
  sexp_gc_var1(res);
  if (etype == SEXP_U8)
    return sexp_make_bytes(ctx, len, SEXP_ZERO);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, elt_type);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, len);
  if (etype < SEXP_U1 || etype >= SEXP_END_OF_UNIFORM_TYPES)
    return sexp_xtype_exception(ctx, self, "unknown uniform vector type", elt_type);
  if (elen < 0)
    return sexp_xtype_exception(ctx, self, "negative length", len);
  sexp_gc_preserve1(ctx, res);
  res = sexp_alloc_type(ctx, uvector, SEXP_UNIFORM_VECTOR);
  if (!sexp_exceptionp(res)) {
    clen = ((elen * sexp_uvector_element_size(etype)) + 7) / 8;
    sexp_uvector_type(res) = etype;
    sexp_uvector_length(res) = elen;
    sexp_uvector_bytes(res) = sexp_make_bytes(ctx, sexp_make_fixnum(clen), SEXP_ZERO);
    if (sexp_exceptionp(sexp_uvector_bytes(res)))
      res = sexp_uvector_bytes(res);
  }
  sexp_gc_release1(ctx);
  return res;
}
#endif

#if SEXP_USE_UTF8_STRINGS

int sexp_utf8_initial_byte_count (int c) {
  if (c < 0xC0) return 1;
  if (c < 0xE0) return 2;
  return ((c>>4)&1)+3;
}

int sexp_utf8_char_byte_count (int c) {
  if (c < 0x80) return 1;
  if (c < 0x800) return 2;
  if (c < 0x10000) return 3;
  return 4;
}

sexp_uint_t sexp_string_utf8_length (unsigned char *p, long len) {
  unsigned char *q = p+len;
  sexp_uint_t i;
  for (i=0; p<q; i++)
    p += sexp_utf8_initial_byte_count(*p);
  return i;
}

char* sexp_string_utf8_prev (unsigned char *p) {
  while ((*--p)>>6 == 2)
    ;
  return (char*)p;
}

sexp sexp_string_utf8_ref (sexp ctx, sexp str, sexp i) {
  unsigned char *p=(unsigned char*)sexp_string_data(str) + sexp_unbox_string_cursor(i);
  if (*p < 0x80)
    return sexp_make_character(*p);
  else if ((*p < 0xC0) || (*p > 0xF7))
    return sexp_user_exception(ctx, NULL, "string-ref: invalid utf8 byte", i);
  else if (*p < 0xE0)
    return sexp_make_character(((p[0]&0x3F)<<6) + (p[1]&0x3F));
  else if (*p < 0xF0)
    return sexp_make_character(((p[0]&0x1F)<<12) + ((p[1]&0x3F)<<6) + (p[2]&0x3F));
  else
    return sexp_make_character(((p[0]&0x0F)<<18) + ((p[1]&0x3F)<<12) + ((p[2]&0x3F)<<6) + (p[3]&0x3F));
}

void sexp_utf8_encode_char (unsigned char* p, int len, int c) {
  switch (len) {
  case 4:  *p++ = (0xF0 + ((c)>>18)); *p++ = (0x80 + ((c>>12)&0x3F));
    *p++ = (0x80 + ((c>>6)&0x3F));    *p = (0x80 + (c&0x3F)); break;
  case 3:  *p++ = (0xE0 + ((c)>>12)); *p++ = (0x80 + ((c>>6)&0x3F));
    *p = (0x80 + (c&0x3F)); break;
  case 2:  *p++ = (0xC0 + ((c)>>6));  *p = (0x80 + (c&0x3F)); break;
  default: *p = c; break;
  }
}

sexp sexp_string_index_to_cursor (sexp ctx, sexp self, sexp_sint_t n, sexp str, sexp index) {
#if SEXP_USE_STRING_INDEX_TABLE
  sexp charlens;
  sexp_sint_t* chunklens;
  sexp_sint_t chunk;
#endif
  sexp cursor;
  sexp_sint_t i, j, limit;
  unsigned char *p;
#if SEXP_USE_STRING_REF_CACHE
  unsigned char *q;
#endif
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, index);
  p = (unsigned char*)sexp_string_data(str);
  limit = sexp_string_size(str);
  i = sexp_unbox_fixnum(index);
  j = 0;
#if SEXP_USE_STRING_INDEX_TABLE
  if (i > SEXP_STRING_INDEX_TABLE_CHUNK_SIZE) {
    charlens = sexp_string_charlens(str);
    if (charlens) {
      chunklens = (sexp_sint_t*)sexp_bytes_data(charlens);
      chunk = i / SEXP_STRING_INDEX_TABLE_CHUNK_SIZE - 1;
      j = chunklens[chunk];
      i -= (chunk+1) * SEXP_STRING_INDEX_TABLE_CHUNK_SIZE;
    }
  }
#elif SEXP_USE_STRING_REF_CACHE
  if (i > (sexp_cached_char_idx(str) + ((sexp_string_length(str) - sexp_cached_char_idx(str)) >> 1))) {
    j = sexp_string_size(str);
    i = -(sexp_string_length(str) - i);
  } else if (i > (sexp_cached_char_idx(str) >> 1)) {
    j = sexp_unbox_string_cursor(sexp_cached_cursor(str));
    i -= sexp_cached_char_idx(str);
  }
#endif

#if SEXP_USE_STRING_REF_CACHE
  if (i >= 0) {
#endif
    for ( ; i>0 && j<limit; i--)
      j += sexp_utf8_initial_byte_count(p[j]);
#if SEXP_USE_STRING_REF_CACHE
  } else {
    for (q=p+j; i<0 && q>=p; i++)
      q = (unsigned char*)sexp_string_utf8_prev(q);
    j = q - p;
  }
#endif

  if (i != 0)
    return sexp_user_exception(ctx, self, "string-index->cursor: index out of range", index);
  cursor = sexp_make_string_cursor(j);
#if SEXP_USE_STRING_REF_CACHE
  sexp_cached_char_idx(str) = sexp_unbox_fixnum(index);
  sexp_cached_cursor(str) = cursor;
#endif
  return cursor;
}

sexp sexp_string_cursor_to_index (sexp ctx, sexp self, sexp_sint_t n, sexp str, sexp offset) {
  sexp_sint_t off = sexp_unbox_string_cursor(offset);
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  sexp_assert_type(ctx, sexp_string_cursorp, SEXP_STRING_CURSOR, offset);
  if (off < 0 || off > (sexp_sint_t)sexp_string_size(str))
    return sexp_user_exception(ctx, self, "string-cursor->index: offset out of range", offset);
#if SEXP_USE_STRING_REF_CACHE
  sexp_uint_t cached_idx = sexp_cached_char_idx(str);
  sexp_sint_t cached_off = sexp_unbox_string_cursor(sexp_cached_cursor(str));
  unsigned char* string_data = (unsigned char*)sexp_string_data(str);
  sexp_sint_t idx_delta;
  if (off >= cached_off) {
    idx_delta = sexp_string_utf8_length(string_data+cached_off, off-cached_off);
  } else {
    idx_delta = 0 - sexp_string_utf8_length(string_data+off, cached_off-off);
  }

  sexp_uint_t new_idx = cached_idx + idx_delta;
  sexp_cached_char_idx(str) = new_idx;
  sexp_cached_cursor(str) = offset;
  return sexp_make_fixnum(new_idx);
#else
  return sexp_make_fixnum(sexp_string_utf8_length((unsigned char*)sexp_string_data(str), off));
#endif
}

sexp sexp_string_cursor_offset (sexp ctx, sexp self, sexp_sint_t n, sexp cur) {
  sexp_assert_type(ctx, sexp_string_cursorp, SEXP_STRING_CURSOR, cur);
  return sexp_make_fixnum(sexp_unbox_string_cursor(cur));
}

#endif

#if SEXP_USE_STRING_INDEX_TABLE
void sexp_update_string_index_lookup(sexp ctx, sexp s) {
  unsigned char *p;
  sexp_sint_t numchunks, len, i, *chunks;
  sexp_gc_var1(tmp);
  if (sexp_string_size(s) < SEXP_STRING_INDEX_TABLE_CHUNK_SIZE*1.2) {
    sexp_string_charlens(s) = NULL; /* don't build table for just a few chars */
    return;
  }
  sexp_gc_preserve1(ctx, tmp);
  tmp = s;
  len = sexp_string_utf8_length((unsigned char*) sexp_string_data(s), sexp_string_size(s));
  numchunks = ((len + SEXP_STRING_INDEX_TABLE_CHUNK_SIZE - 1) / SEXP_STRING_INDEX_TABLE_CHUNK_SIZE) - 1;
  sexp_string_charlens(s) =
    sexp_make_bytes_op(ctx, NULL, 2, sexp_make_fixnum(numchunks * sizeof(sexp_sint_t)), SEXP_VOID);
  chunks = (sexp_sint_t*)sexp_bytes_data(sexp_string_charlens(s));
  p = (unsigned char*) sexp_string_data(s);
  i = 0;
  while (1) {
    p += sexp_utf8_initial_byte_count(*p);
    if (++i % SEXP_STRING_INDEX_TABLE_CHUNK_SIZE == 0) {
      chunks[i/SEXP_STRING_INDEX_TABLE_CHUNK_SIZE - 1] = p - (unsigned char*) sexp_string_data(s);
      if (i / SEXP_STRING_INDEX_TABLE_CHUNK_SIZE >= numchunks-1)
        break;
    }
  }
  sexp_gc_release1(ctx);
}
#endif

sexp sexp_make_string_op (sexp ctx, sexp self, sexp_sint_t n, sexp len, sexp ch)
{
  sexp i = (sexp_charp(ch) ? sexp_make_fixnum(sexp_unbox_character(ch)) : ch);
#if SEXP_USE_PACKED_STRINGS
  sexp b;
#else
  sexp_gc_var2(b, s);
#endif
#if SEXP_USE_UTF8_STRINGS
  int j, clen;
  if (sexp_charp(ch) && (sexp_unbox_character(ch) >= 0x80)) {
    sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, len);
    clen = sexp_utf8_char_byte_count(sexp_unbox_character(ch));
    b = sexp_make_bytes_op(ctx, self, n,
                           sexp_fx_mul(len, sexp_make_fixnum(clen)), SEXP_VOID);
    if (sexp_exceptionp(b)) return b;
    for (j=0; j<sexp_unbox_fixnum(len); j++)
      sexp_utf8_encode_char((unsigned char*)sexp_bytes_data(b)+(j*clen), clen,
                            sexp_unbox_character(ch));
  } else
#endif
  b = sexp_make_bytes_op(ctx, self, n, len, i);
  if (sexp_exceptionp(b)) return b;
#if SEXP_USE_PACKED_STRINGS
  sexp_pointer_tag(b) = SEXP_STRING;
  return b;
#else
  sexp_gc_preserve2(ctx, b, s);
  s = sexp_alloc_type(ctx, string, SEXP_STRING);
  sexp_string_bytes(s) = b;
  sexp_string_offset(s) = 0;
  sexp_string_size(s) = sexp_bytes_length(b);
#if SEXP_USE_STRING_REF_CACHE
  sexp_cached_char_idx(s) = 0;
  sexp_cached_cursor(s) = sexp_make_string_cursor(0);
#endif
  sexp_update_string_index_lookup(ctx, s);
  sexp_gc_release2(ctx);
  return s;
#endif
}

sexp sexp_c_string (sexp ctx, const char *str, sexp_sint_t slen) {
  sexp_sint_t len;
  sexp s;
  if (str == NULL) return SEXP_FALSE;
  len = ((slen >= 0) ? slen : strlen(str));
  s = sexp_make_string(ctx, sexp_make_fixnum(len), SEXP_VOID);
  if (sexp_exceptionp(s)) return s;
  memcpy(sexp_string_data(s), str, len);
  sexp_string_data(s)[len] = '\0';
  sexp_update_string_index_lookup(ctx, s);
  return s;
}

sexp sexp_substring_op (sexp ctx, sexp self, sexp_sint_t n, sexp str, sexp start, sexp end) {
  sexp res;
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  sexp_assert_type(ctx, sexp_string_cursorp, SEXP_STRING_CURSOR, start);
  if (sexp_not(end))
    end = sexp_make_string_cursor(sexp_string_size(str));
  sexp_assert_type(ctx, sexp_string_cursorp, SEXP_STRING_CURSOR, end);
  if ((sexp_unbox_string_cursor(start) < 0)
      || (sexp_unbox_string_cursor(start) > (sexp_sint_t)sexp_string_size(str))
      || (sexp_unbox_string_cursor(end) < 0)
      || (sexp_unbox_string_cursor(end) > (sexp_sint_t)sexp_string_size(str))
      || (end < start))
    return sexp_range_exception(ctx, str, start, end);
  res = sexp_make_string(ctx, sexp_make_fixnum(sexp_unbox_string_cursor(end) - sexp_unbox_string_cursor(start)), SEXP_VOID);
  memcpy(sexp_string_data(res),
         sexp_string_data(str)+sexp_unbox_string_cursor(start),
         sexp_string_size(res));
  sexp_string_data(res)[sexp_string_size(res)] = '\0';
  sexp_update_string_index_lookup(ctx, res);
  return res;
}

sexp sexp_subbytes_op (sexp ctx, sexp self, sexp_sint_t n, sexp vec, sexp start, sexp end) {
  sexp res;
  sexp_gc_var1(str);
  sexp_assert_type(ctx, sexp_bytesp, SEXP_BYTES, vec);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, start);
  if (sexp_not(end))
    end = sexp_make_fixnum(sexp_bytes_length(vec));
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, end);
  sexp_gc_preserve1(ctx, str);
#if SEXP_USE_PACKED_STRINGS
  str = sexp_c_string(ctx, sexp_bytes_data(vec), sexp_bytes_length(vec));
#else
  str = sexp_alloc_type(ctx, string, SEXP_STRING);
  sexp_string_bytes(str) = vec;
  sexp_string_offset(str) = 0;
  sexp_string_size(str) = sexp_bytes_length(vec);
#endif
  res = sexp_substring_op(ctx, self, n, str, sexp_fixnum_to_string_cursor(start), sexp_fixnum_to_string_cursor(end));
  if (!sexp_exceptionp(res))
    res = sexp_string_to_bytes(ctx, res);
  sexp_gc_release1(ctx);
  return res;
}

#if SEXP_USE_UTF8_STRINGS
sexp sexp_utf8_substring_op (sexp ctx, sexp self, sexp_sint_t n, sexp str, sexp start, sexp end) {
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, start);
  start = sexp_string_index_to_cursor(ctx, self, n, str, start);
  if (sexp_exceptionp(start)) return start;
  if (sexp_fixnump(end)) {
    end = sexp_string_index_to_cursor(ctx, self, n, str, end);
    if (sexp_exceptionp(end)) return end;
  }
  return sexp_substring_op(ctx, self, n, str, start, end);
}
#endif

sexp sexp_string_concatenate_op (sexp ctx, sexp self, sexp_sint_t n, sexp str_ls, sexp sep) {
  sexp res, ls;
  sexp_uint_t len=0, i=0, sep_len=0;
  char *p, *csep=NULL;
  for (ls=str_ls; sexp_pairp(ls); ls=sexp_cdr(ls), i++)
    if (! sexp_stringp(sexp_car(ls)))
      return sexp_type_exception(ctx, self, SEXP_STRING, sexp_car(ls));
    else
      len += sexp_string_size(sexp_car(ls));
  if ((i > 0) && sexp_stringp(sep) && ((sep_len=sexp_string_size(sep)) > 0)) {
    csep = sexp_string_data(sep);
    len += sep_len*(i-1);
  }
  res = sexp_make_string(ctx, sexp_make_fixnum(len), SEXP_VOID);
  p = sexp_string_data(res);
  for (ls=str_ls; sexp_pairp(ls); ls=sexp_cdr(ls)) {
    len = sexp_string_size(sexp_car(ls));
    memcpy(p, sexp_string_data(sexp_car(ls)), len);
    p += len;
    if (sep_len && sexp_pairp(sexp_cdr(ls))) {
      memcpy(p, csep, sep_len);
      p += sep_len;
    }
  }
  *p = '\0';
  sexp_update_string_index_lookup(ctx, res);
  return res;
}

#define FNV_PRIME 16777619
#define FNV_OFFSET_BASIS ((sexp_sint_t)2166136261)

#if SEXP_USE_HASH_SYMS

static sexp_uint_t sexp_string_hash(const char *str, sexp_sint_t len,
                                    sexp_uint_t acc) {
  for ( ; len; len--) {acc *= FNV_PRIME; acc ^= *str++;}
  return acc;
}

#endif

sexp sexp_intern(sexp ctx, const char *str, sexp_sint_t len) {
#if SEXP_USE_HUFF_SYMS
  struct sexp_huff_entry he;
  sexp_sint_t space, newbits;
  char c;
#endif
  sexp ls, tmp;
  sexp_gc_var1(sym);
  sexp_sint_t bucket=0;
#if (SEXP_USE_HASH_SYMS || SEXP_USE_HUFF_SYMS)
  sexp_sint_t i=0, res=FNV_OFFSET_BASIS;
  const char *p=str;
#endif

  if (len < 0) len = strlen(str);

#if SEXP_USE_HUFF_SYMS
  res = 0;
  space = SEXP_IMMEDIATE_BITS;
  if (len == 0 || sexp_isdigit((unsigned char)p[0])
      || ((p[0] == '+' || p[0] == '-') && len > 1))
    goto normal_intern;
  for ( ; i<len; i++, p++) {
    c = *p;
    if ((unsigned char)c <= 32 || (unsigned char)c > 127 || c == '\\' || c == '|' || c == '.' || c =='#' || sexp_is_separator(c))
      goto normal_intern;
    he = huff_table[(unsigned char)c];
    newbits = he.len;
    if ((space+newbits) > (sizeof(sexp)*8))
      goto normal_intern;
    res |= (((sexp_uint_t) he.bits) << space);
    space += newbits;
  }
  return (sexp) (res + SEXP_ISYMBOL_TAG);

 normal_intern:
#endif
#if SEXP_USE_HASH_SYMS
  bucket = (sexp_string_hash(p, len-i, res) % SEXP_SYMBOL_TABLE_SIZE);
#endif
  for (ls=sexp_context_symbols(ctx)[bucket]; sexp_pairp(ls); ls=sexp_cdr(ls))
    if ((sexp_lsymbol_length(tmp=sexp_car(ls)) == len)
        && ! strncmp(str, sexp_lsymbol_data(tmp), len))
      return sexp_car(ls);

  /* not found, make a new symbol */
  sexp_gc_preserve1(ctx, sym);
  sym = sexp_c_string(ctx, str, len);
  if (sexp_exceptionp(sym)) return sym;
#if ! SEXP_USE_PACKED_STRINGS
  sym = sexp_string_bytes(sym);
#endif
  sexp_pointer_tag(sym) = SEXP_SYMBOL;
  sexp_push(ctx, sexp_context_symbols(ctx)[bucket], sym);
  sexp_gc_release1(ctx);
  return sym;
}

sexp sexp_string_to_symbol_op (sexp ctx, sexp self, sexp_sint_t n, sexp str) {
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  return sexp_intern(ctx, sexp_string_data(str), sexp_string_size(str));
}

sexp sexp_make_vector_op (sexp ctx, sexp self, sexp_sint_t n, sexp len, sexp dflt) {
  sexp vec, *x;
  sexp_sint_t i, clen = sexp_unbox_fixnum(len);
  if (! clen) return sexp_global(ctx, SEXP_G_EMPTY_VECTOR);
  if (clen < 0 || clen > SEXP_MAX_VECTOR_LENGTH)
    return sexp_xtype_exception(ctx, self, "vector length out of range", len);
  vec = sexp_alloc_tagged(ctx, sexp_sizeof(vector) + clen*sizeof(sexp),
                          SEXP_VECTOR);
  if (sexp_exceptionp(vec)) return vec;
  x = sexp_vector_data(vec);
  for (i=0; i<clen; i++)
    x[i] = dflt;
  sexp_vector_length(vec) = clen;
  return vec;
}

sexp sexp_list_to_vector_op (sexp ctx, sexp self, sexp_sint_t n, sexp ls) {
  int i;
  sexp x, *elts, vec = sexp_make_vector(ctx, sexp_length(ctx, ls), SEXP_VOID);
  if (sexp_exceptionp(vec)) return vec;
  elts = sexp_vector_data(vec);
  for (i=0, x=ls; sexp_pairp(x); i++, x=sexp_cdr(x))
    elts[i] = sexp_car(x);
  return vec;
}

sexp sexp_make_cpointer (sexp ctx, sexp_uint_t type_id, void *value,
                         sexp parent, int freep) {
  sexp ptr;
  if (! value) return SEXP_FALSE;
  ptr = sexp_alloc_type(ctx, cpointer, type_id);
  if (sexp_exceptionp(ptr)) return ptr;
  sexp_freep(ptr) = freep;
  sexp_cpointer_value(ptr) = value;
  sexp_cpointer_parent(ptr) = parent;
  sexp_cpointer_length(ptr) = 0;
  return ptr;
}

/************************ reading and writing *************************/

/* start 4 bytes in so we can always unread a utf8 char in peek-char */
#define BUF_START 4

int sexp_buffered_read_char (sexp ctx, sexp p) {
  sexp_gc_var2(tmp, origbytes);
  int res = 0;
  if (sexp_port_offset(p) < sexp_port_size(p)) {
    return ((unsigned char*)sexp_port_buf(p))[sexp_port_offset(p)++];
  } else if (!sexp_port_openp(p)) {
    return EOF;
  } else if (sexp_port_stream(p)) {
    res = fread(sexp_port_buf(p) + BUF_START, 1, SEXP_PORT_BUFFER_SIZE - BUF_START, sexp_port_stream(p));
    if (res >= 0) {
      sexp_port_offset(p) = BUF_START;
      sexp_port_size(p) = res + BUF_START;
      res = ((sexp_port_offset(p) < sexp_port_size(p))
             ? ((unsigned char*)sexp_port_buf(p))[sexp_port_offset(p)++] : EOF);
    }
  } else if (sexp_filenop(sexp_port_fd(p))) {
    res = read(sexp_port_fileno(p), sexp_port_buf(p) + BUF_START, SEXP_PORT_BUFFER_SIZE - BUF_START);
    if (res >= 0) {
      sexp_port_offset(p) = BUF_START;
      sexp_port_size(p) = res + BUF_START;
      res = ((sexp_port_offset(p) < sexp_port_size(p))
             ? ((unsigned char*)sexp_port_buf(p))[sexp_port_offset(p)++] : EOF);
    }
  } else if (sexp_port_customp(p)) {
    sexp_gc_preserve2(ctx, tmp, origbytes);
    tmp = sexp_list2(ctx, sexp_make_fixnum(BUF_START), sexp_make_fixnum(SEXP_PORT_BUFFER_SIZE));
    origbytes = sexp_port_binaryp(p) && !SEXP_USE_PACKED_STRINGS ? sexp_string_bytes(sexp_port_buffer(p)) : sexp_port_buffer(p);
    tmp = sexp_cons(ctx, origbytes, tmp);
    tmp = sexp_apply(ctx, sexp_port_reader(p), tmp);
    if (sexp_fixnump(tmp) && sexp_unbox_fixnum(tmp) > BUF_START) {
      sexp_port_offset(p) = BUF_START;
      sexp_port_size(p) = sexp_unbox_fixnum(tmp);
      if (!sexp_port_binaryp(p) && !SEXP_USE_PACKED_STRINGS
          && origbytes != sexp_string_bytes(sexp_port_buffer(p))) {
        /* handle resize */
        memcpy(sexp_port_buf(p), sexp_string_data(sexp_port_buffer(p)), sexp_port_size(p));
      }
      res = ((sexp_port_offset(p) < sexp_port_size(p))
             ? ((unsigned char*)sexp_port_buf(p))[sexp_port_offset(p)++] : EOF);
    } else {
      res = EOF;
      sexp_port_size(p) = 0;
    }
    sexp_gc_release2(ctx);
  } else {
    res = EOF;
  }
  return res;
}

int sexp_buffered_write_char (sexp ctx, int c, sexp p) {
  int res;
  if (sexp_port_offset(p)+1 >= sexp_port_size(p))
    if ((res = sexp_buffered_flush(ctx, p, 0)))
      return res;
  sexp_port_buf(p)[sexp_port_offset(p)++] = c;
  return 0;
}

int sexp_buffered_write_string_n (sexp ctx, const char *str,
                                  sexp_uint_t len, sexp p) {
  int diff, res, written=0;
  while (sexp_port_offset(p)+len >= sexp_port_size(p)) {
    diff = sexp_port_size(p) - sexp_port_offset(p);
    memcpy(sexp_port_buf(p)+sexp_port_offset(p), str, diff);
    sexp_port_offset(p) = sexp_port_size(p);
    if ((res = sexp_buffered_flush(ctx, p, 0)))
      return written + diff;
    written += sexp_port_size(p);
    str += diff;
    len -= diff;
  }
  memcpy(sexp_port_buf(p)+sexp_port_offset(p), str, len);
  sexp_port_offset(p) += len;
  return written + len;
}

int sexp_buffered_write_string (sexp ctx, const char *str, sexp p) {
  return sexp_buffered_write_string_n(ctx, str, strlen(str), p);
}

int sexp_buffered_flush (sexp ctx, sexp p, int forcep) {
  sexp_sint_t res = 0, off;
  sexp_gc_var1(tmp);
  if (!sexp_oportp(p) || (!forcep && !sexp_port_openp(p)))
    return -1;
  off = sexp_port_offset(p);
  if (sexp_port_stream(p)) {
    if (off > 0) fwrite(sexp_port_buf(p), 1, off, sexp_port_stream(p));
    res = fflush(sexp_port_stream(p));
  } else if (sexp_filenop(sexp_port_fd(p))) {
    if (off > 0)
      res = write(sexp_fileno_fd(sexp_port_fd(p)), sexp_port_buf(p), off);
    if (res < off) {
      if (res > 0) {
        memmove(sexp_port_buf(p), sexp_port_buf(p) + res, off - res);
        sexp_port_offset(p) = off - res;
        res = 0;
      } else {
        res = -1;
      }
    } else {
      sexp_port_offset(p) = 0;
      res = 0;
    }
  } else if (!sexp_port_openp(p)) {
    return -1;
  } else if (sexp_port_offset(p) > 0) {
    sexp_gc_preserve1(ctx, tmp);
    if (sexp_port_customp(p)) {   /* custom port */
      tmp = sexp_list2(ctx, SEXP_ZERO, sexp_make_fixnum(sexp_port_offset(p)));
      tmp = sexp_cons(ctx, sexp_port_binaryp(p) ? sexp_string_bytes(sexp_port_buffer(p)) : sexp_port_buffer(p), tmp);
      tmp = sexp_apply(ctx, sexp_port_writer(p), tmp);
      sexp_port_offset(p) = 0;
      res = (sexp_fixnump(tmp) && sexp_unbox_fixnum(tmp) > 0) ? 0 : -1;
    } else {                      /* string port */
      tmp = sexp_c_string(ctx, sexp_port_buf(p), off);
      if (tmp && sexp_stringp(tmp)) {
        sexp_push(ctx, sexp_cdr(sexp_port_cookie(p)), tmp);
        sexp_port_offset(p) = 0;
        res = 0;
      } else {
        res = -1;
      }
    }
    sexp_gc_release1(ctx);
  }
  return res;
}

sexp sexp_open_input_string_op (sexp ctx, sexp self, sexp_sint_t n, sexp str) {
  sexp res;
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  res = sexp_make_input_port(ctx, NULL, SEXP_FALSE);
  if (sexp_exceptionp(res)) return res;
  sexp_port_cookie(res) = str;
  sexp_port_buf(res) = sexp_string_data(str);
  sexp_port_offset(res) = 0;
  sexp_port_size(res) = sexp_string_size(str);
  sexp_port_binaryp(res) = 0;
  return res;
}

sexp sexp_open_output_string_op (sexp ctx, sexp self, sexp_sint_t n) {
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = sexp_make_output_port(ctx, NULL, SEXP_FALSE);
  if (!sexp_exceptionp(res)) {
    sexp_port_cookie(res) = sexp_cons(ctx, SEXP_FALSE, SEXP_NULL);
    sexp_car(sexp_port_cookie(res)) =
      sexp_make_bytes(ctx, sexp_make_fixnum(SEXP_PORT_BUFFER_SIZE), SEXP_VOID);
    if (sexp_exceptionp(sexp_car(sexp_port_cookie(res)))) {
      res = sexp_car(sexp_port_cookie(res));
    } else {
      sexp_port_buf(res) = sexp_bytes_data(sexp_car(sexp_port_cookie(res)));
      sexp_port_size(res) = SEXP_PORT_BUFFER_SIZE;
      sexp_port_offset(res) = 0;
      sexp_port_binaryp(res) = 0;
    }
  }
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_get_output_string_op (sexp ctx, sexp self, sexp_sint_t n, sexp out) {
  sexp res;
  sexp_gc_var3(ls, rev, tmp);
  sexp_assert_type(ctx, sexp_oportp, SEXP_OPORT, out);
  if (!sexp_port_openp(out))
    return sexp_xtype_exception(ctx, self, "output port is closed", out);
  if (!sexp_pairp(sexp_port_cookie(out)))
    return sexp_xtype_exception(ctx, self, "not a string output port", out);
  sexp_gc_preserve3(ctx, ls, rev, tmp);
  if (sexp_port_offset(out) > 0) {
    tmp = sexp_c_string(ctx, sexp_port_buf(out), sexp_port_offset(out));
    rev = sexp_cons(ctx, tmp, sexp_cdr(sexp_port_cookie(out)));
  } else {
    rev = sexp_cdr(sexp_port_cookie(out));
  }
  ls = sexp_reverse(ctx, rev);
  res = SEXP_FALSE;
  for (tmp = ls; sexp_pairp(tmp); tmp = sexp_cdr(tmp))
    if (!sexp_stringp(sexp_car(tmp)))
      res = sexp_xtype_exception(ctx, self, "not an output string port", out);
  if (!sexp_nullp(tmp))
    res = sexp_xtype_exception(ctx, self, "not an output string port", out);
  if (!sexp_exceptionp(res))
    res = sexp_string_concatenate(ctx, ls, SEXP_FALSE);
  sexp_gc_release3(ctx);
  return res;
}

sexp sexp_open_input_file_descriptor (sexp ctx, sexp self, sexp_sint_t n, sexp fileno, sexp shutdownp) {
  sexp_gc_var2(res, str);
  sexp_assert_type(ctx, sexp_filenop, SEXP_FILENO, fileno);
  if (sexp_fileno_fd(fileno) < 0)
    return sexp_file_exception(ctx, self, "invalid file descriptor", fileno);
  sexp_gc_preserve2(ctx, res, str);
  str = sexp_make_string(ctx, sexp_make_fixnum(SEXP_PORT_BUFFER_SIZE), SEXP_VOID);
  res = sexp_open_input_string(ctx, str);
  if (!sexp_exceptionp(res)) {
    sexp_port_fd(res) = fileno;
    sexp_port_offset(res) = SEXP_PORT_BUFFER_SIZE;
    sexp_port_binaryp(res) = 1;
    sexp_port_shutdownp(res) = sexp_truep(shutdownp);
    sexp_fileno_count(fileno)++;
  }
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_open_output_file_descriptor (sexp ctx, sexp self, sexp_sint_t n, sexp fileno, sexp shutdownp) {
  sexp res = sexp_open_input_file_descriptor(ctx, self, n, fileno, shutdownp);
  if (!sexp_exceptionp(res)) {
    sexp_pointer_tag(res) = SEXP_OPORT;
    sexp_port_offset(res) = 0;
  }
  return res;
}

#if SEXP_USE_WEAK_REFERENCES
sexp sexp_make_ephemeron_op(sexp ctx, sexp self, sexp_sint_t n, sexp key, sexp value) {
  sexp res = sexp_alloc_type(ctx, pair, SEXP_EPHEMERON);
  if (!sexp_exceptionp(res)) {
    sexp_global(ctx, SEXP_G_WEAK_OBJECTS_PRESENT) = SEXP_TRUE;
    sexp_ephemeron_key(res) = key;
    sexp_ephemeron_value(res) = value;
  }
  return res;
}
#endif /* SEXP_USE_WEAK_REFERENCES */

#if SEXP_USE_UNIFY_FILENOS_BY_NUMBER
static sexp* sexp_fileno_cell(sexp ctx, sexp vec, int fd) {
  sexp *data;
  sexp_sint_t i, cell, len;
  if (!sexp_vectorp(vec))
    return NULL;
  len = sexp_vector_length(vec);
  if (len == 0)
    return NULL;
  data = sexp_vector_data(vec);
  cell = (fd * FNV_PRIME) % len;
  if (cell < 0) cell += len;
  for (i = 0; i < len; i++, cell=(cell+1)%len)
    if (!sexp_ephemeronp(data[cell])
        || (sexp_filenop(sexp_ephemeron_key(data[cell]))
            && sexp_fileno_fd(sexp_ephemeron_key(data[cell])) == fd))
        return &(data[cell]);
  return NULL;
}

static sexp sexp_lookup_fileno(sexp ctx, int fd) {
  sexp* cell = sexp_fileno_cell(ctx, sexp_global(ctx, SEXP_G_FILE_DESCRIPTORS), fd);
  if (cell && sexp_ephemeronp(*cell)
      && sexp_filenop(sexp_ephemeron_key(*cell))
      && sexp_fileno_fd(sexp_ephemeron_key(*cell)) == fd) {
    if (sexp_fileno_openp(sexp_ephemeron_key(*cell)))
      return sexp_ephemeron_key(*cell);
  }
  return SEXP_FALSE;
}

static sexp* sexp_insert_fileno_ephemeron(sexp ctx, sexp vec, sexp eph) {
  sexp *data = sexp_fileno_cell(ctx, vec, sexp_fileno_fd(sexp_ephemeron_key(eph)));
  if (data) *data = eph;
  return data;
}

static void sexp_insert_fileno(sexp ctx, sexp fileno) {
  sexp *data, tmp, vec = sexp_global(ctx, SEXP_G_FILE_DESCRIPTORS);
  sexp_sint_t i, n2, n = sexp_unbox_fixnum(sexp_global(ctx, SEXP_G_NUM_FILE_DESCRIPTORS));
  if (!sexp_vectorp(vec)) {
    vec = sexp_global(ctx, SEXP_G_FILE_DESCRIPTORS)
        = sexp_make_vector(ctx, sexp_make_fixnum(128), SEXP_FALSE);
  } else if (n >= (sexp_sint_t)sexp_vector_length(vec)) {
    data = sexp_vector_data(vec);
    for (i = n2 = 0; i < (sexp_sint_t)sexp_vector_length(vec); i++)
      if (sexp_ephemeronp(data[i]) && !sexp_brokenp(data[i]))
        n2++;
    if (n2 * 2 >= n)
      n2 = n * 2;
    tmp = sexp_global(ctx, SEXP_G_FILE_DESCRIPTORS)
        = sexp_make_vector(ctx, sexp_make_fixnum(n2), SEXP_FALSE);
    for (i = n = 0; i < (sexp_sint_t)sexp_vector_length(vec); i++)
      if (sexp_ephemeronp(data[i]) && !sexp_brokenp(data[i])
          && sexp_insert_fileno_ephemeron(ctx, tmp, data[i]))
        n++;
    vec = tmp;
  }
  if (sexp_insert_fileno_ephemeron(ctx, sexp_global(ctx, SEXP_G_FILE_DESCRIPTORS), sexp_make_ephemeron(ctx, fileno, SEXP_FALSE)))
    n++;
  sexp_global(ctx, SEXP_G_NUM_FILE_DESCRIPTORS) = sexp_make_fixnum(n);
}
#endif  /* SEXP_USE_UNIFY_FILENOS_BY_NUMBER */

sexp sexp_make_fileno_op (sexp ctx, sexp self, sexp_sint_t n, sexp fd, sexp no_closep) {
  sexp_gc_var1(res);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, fd);
  if (sexp_unbox_fixnum(fd) < 0) return SEXP_FALSE;
#if SEXP_USE_UNIFY_FILENOS_BY_NUMBER
  res = sexp_lookup_fileno(ctx, sexp_unbox_fixnum(fd));
  if (sexp_filenop(res)) {
    sexp_fileno_no_closep(res) = sexp_truep(no_closep);
    sexp_fileno_openp(res) = 1;  /* not necessarily */
    return res;
  }
#endif
  sexp_gc_preserve1(ctx, res);
  res = sexp_alloc_type(ctx, fileno, SEXP_FILENO);
  if (!sexp_exceptionp(res)) {
    sexp_fileno_fd(res) = sexp_unbox_fixnum(fd);
    sexp_fileno_openp(res) = 1;
    sexp_fileno_no_closep(res) = sexp_truep(no_closep);
#if SEXP_USE_UNIFY_FILENOS_BY_NUMBER
    sexp_insert_fileno(ctx, res);
#endif
  }
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_make_input_port (sexp ctx, FILE* in, sexp name) {
  sexp p = sexp_alloc_type(ctx, port, SEXP_IPORT);
  if (sexp_exceptionp(p)) return p;
  sexp_port_stream(p) = in;
  sexp_port_name(p) = name;
  sexp_port_line(p) = 1;
  sexp_port_flags(p) = SEXP_PORT_UNKNOWN_FLAGS;
  sexp_port_buf(p) = NULL;
  sexp_port_fd(p) = SEXP_FALSE;
  sexp_port_openp(p) = 1;
  sexp_port_bidirp(p) = 0;
  sexp_port_binaryp(p) = 1;
  sexp_port_shutdownp(p) = 0;
  sexp_port_no_closep(p) = 0;
  sexp_port_sourcep(p) = 0;
  sexp_port_blockedp(p) = 0;
#if SEXP_USE_FOLD_CASE_SYMS
  sexp_port_fold_casep(p) = sexp_truep(sexp_global(ctx, SEXP_G_FOLD_CASE_P));
#endif
#if SEXP_USE_UNIFY_FILENOS_BY_NUMBER
  /* if the fd was previously opened by a non-stream port, preserve it */
  /* here to avoid gc timing issues */
  if (in && fileno(in) >= 0) {
    sexp_port_fd(p) = sexp_lookup_fileno(ctx, fileno(in));
    if (sexp_filenop(sexp_port_fd(p))) {
      sexp_fileno_openp(sexp_port_fd(p)) = 1;
      ++sexp_fileno_count(sexp_port_fd(p));
    }
  }
#endif
  sexp_port_cookie(p) = SEXP_VOID;
  return p;
}

sexp sexp_make_output_port (sexp ctx, FILE* out, sexp name) {
  sexp p = sexp_make_input_port(ctx, out, name);
  if (sexp_exceptionp(p)) return p;
  sexp_pointer_tag(p) = SEXP_OPORT;
  return p;
}

sexp sexp_make_non_null_input_port (sexp ctx, FILE* in, sexp name) {
  if (!in) return sexp_user_exception(ctx, SEXP_FALSE, "null input-port", name);
  return sexp_make_input_port(ctx, in, name);
}

sexp sexp_make_non_null_output_port (sexp ctx, FILE* out, sexp name) {
  if (!out) return sexp_user_exception(ctx, SEXP_FALSE, "null output-port", name);
  return sexp_make_output_port(ctx, out, name);
}

#if SEXP_USE_BIDIRECTIONAL_PORTS
sexp sexp_make_non_null_input_output_port (sexp ctx, FILE* io, sexp name) {
  sexp res;
  if (!io) return sexp_user_exception(ctx, SEXP_FALSE, "null input-output-port", name);
  res = sexp_make_input_port(ctx, io, name);
  if (sexp_portp(res)) sexp_port_bidirp(res) = 1;
  return res;
}
#else
#define sexp_make_non_null_input_output_port sexp_make_non_null_input_port
#endif

sexp sexp_port_outputp_op (sexp ctx, sexp self, sexp_sint_t n, sexp obj) {
  return sexp_make_boolean(sexp_oportp(obj));
}

sexp sexp_port_binaryp_op (sexp ctx, sexp self, sexp_sint_t n, sexp obj) {
  return sexp_make_boolean(sexp_portp(obj) && sexp_port_binaryp(obj));
}

sexp sexp_port_openp_op (sexp ctx, sexp self, sexp_sint_t n, sexp obj) {
  return sexp_make_boolean(sexp_portp(obj) && sexp_port_openp(obj));
}

#if SEXP_USE_FOLD_CASE_SYMS
sexp sexp_get_port_fold_case (sexp ctx, sexp self, sexp_sint_t n, sexp in) {
  sexp_assert_type(ctx, sexp_iportp, SEXP_IPORT, in);
  return sexp_make_boolean(sexp_port_fold_casep(in));
}
sexp sexp_set_port_fold_case (sexp ctx, sexp self, sexp_sint_t n, sexp in, sexp x) {
  sexp_assert_type(ctx, sexp_iportp, SEXP_IPORT, in);
  sexp_assert_type(ctx, sexp_booleanp, SEXP_BOOLEAN, x);
  sexp_port_fold_casep(in) = sexp_truep(x);
  return SEXP_VOID;
}
#endif

#if SEXP_USE_GREEN_THREADS
int sexp_maybe_block_port (sexp ctx, sexp in, int forcep) {
  sexp f;
  int c;
  if ((sexp_port_stream(in) || sexp_filenop(sexp_port_fd(in)))
      && sexp_port_fileno(in) >= 0) {
    if (sexp_port_flags(in) == SEXP_PORT_UNKNOWN_FLAGS)
      sexp_port_flags(in) = fcntl(sexp_port_fileno(in), F_GETFL);
    if (sexp_port_flags(in) & O_NONBLOCK) {
      if (!forcep
          && (((c = sexp_read_char(ctx, in)) == EOF)
              && sexp_port_stream(in)
              && (sexp_port_stream(in) ? ferror(sexp_port_stream(in)) : 1)
              && (errno == EAGAIN))) {
        if (sexp_port_stream(in))
          clearerr(sexp_port_stream(in));
        f = sexp_global(ctx, SEXP_G_THREADS_BLOCKER);
        if (sexp_applicablep(f)) {
          sexp_apply2(ctx, f, in, SEXP_FALSE);
          return 1;
        }
      }
      if (!forcep) sexp_push_char(ctx, c, in);
      sexp_port_blockedp(in) = 1;
      fcntl(sexp_port_fileno(in), F_SETFL, sexp_port_flags(in) & ~O_NONBLOCK);
    }
  }
  return 0;
}

int sexp_maybe_block_output_port (sexp ctx, sexp out) {
  if (sexp_port_stream(out) && sexp_port_fileno(out) >= 0) {
    if (sexp_port_flags(out) == SEXP_PORT_UNKNOWN_FLAGS)
      sexp_port_flags(out) = fcntl(sexp_port_fileno(out), F_GETFL);
    if (sexp_port_flags(out) & O_NONBLOCK) {
      sexp_port_blockedp(out) = 1;
      fcntl(sexp_port_fileno(out), F_SETFL, sexp_port_flags(out) & ~O_NONBLOCK);
      return 1;
    }
  }
  return 0;
}

void sexp_maybe_unblock_port (sexp ctx, sexp port) {
  if (sexp_port_blockedp(port)) {
    sexp_port_blockedp(port) = 0;
    fcntl(sexp_port_fileno(port), F_SETFL, sexp_port_flags(port));
  }
}
#endif

#if SEXP_USE_GREEN_THREADS
static int sexp_fileno_ready_p (int fd) {
  struct pollfd pfd;
  if (fd < 0) return 0;
  pfd.fd = fd;
  pfd.events = POLLIN;
  return poll(&pfd, 1, 0) == 1;
}

static int sexp_stream_ready_p (FILE* in) {
  int flags = fcntl(fileno(in), F_GETFL), res;
  if (! (flags & O_NONBLOCK)) fcntl(fileno(in), F_SETFL, flags & O_NONBLOCK);
  res = getc(in);
  if (! (flags & O_NONBLOCK)) fcntl(fileno(in), F_SETFL, flags);
  if (res == EOF || ferror(in)) {
    clearerr(in);
    return 0;
  }
  ungetc(res, in);
  return 1;
}
#endif

sexp sexp_char_ready_p (sexp ctx, sexp self, sexp_sint_t n, sexp in) {
  sexp_assert_type(ctx, sexp_iportp, SEXP_IPORT, in);
  if (!sexp_port_openp(in))
    return SEXP_FALSE;
  if (sexp_port_buf(in))
    if (sexp_port_offset(in) < sexp_port_size(in)
        || (!sexp_filenop(sexp_port_fd(in)) && !sexp_port_stream(in)))
      return SEXP_TRUE;
#if SEXP_USE_GREEN_THREADS     /* maybe not just when threads are enabled */
  if (sexp_filenop(sexp_port_fd(in)))
    return sexp_make_boolean(sexp_fileno_ready_p(sexp_port_fileno(in)));
  else if (sexp_port_stream(in))
    return sexp_make_boolean(sexp_stream_ready_p(sexp_port_stream(in)));
#endif
  /* for custom ports and unthreaded compiles we just return true for now */
  return SEXP_TRUE;
}

#define NUMBUF_LEN 32

static struct {const char* name; char ch;} sexp_char_names[] = {
  {"newline", '\n'},
  {"return", '\r'},
  {"space", ' '},
  {"tab", '\t'},
#if SEXP_USE_EXTENDED_CHAR_NAMES
  {"alarm", '\a'},
  {"backspace", '\b'},
  {"delete", 127},
  {"escape", 27},
  {"null", 0},
#endif
};

#define sexp_num_char_names (sizeof(sexp_char_names)/sizeof(sexp_char_names[0]))

sexp sexp_write_one (sexp ctx, sexp obj, sexp out, sexp_sint_t bound) {
#if SEXP_USE_HUFF_SYMS
  sexp_uint_t res;
#endif
  sexp_uint_t len, c;
  sexp_sint_t i=0, j, k;
#if SEXP_USE_FLONUMS
  double f, ftmp;
#endif
#if SEXP_USE_BYTEVECTOR_LITERALS && SEXP_BYTEVECTOR_HEX_LITERALS
  char buf[5];
#endif
  sexp x, x2, *elts;
  char *str=NULL, numbuf[NUMBUF_LEN];

  if (! obj) {
    sexp_write_string(ctx, "#<null>", out); /* shouldn't happen */
  } else if (sexp_pointerp(obj)) {
    if (bound >= SEXP_DEFAULT_WRITE_BOUND) {
      sexp_write_string(ctx, "...", out);
      return SEXP_VOID;
    }
    switch (sexp_pointer_tag(obj)) {
    case SEXP_PAIR:
      sexp_write_char(ctx, '(', out);
      sexp_write_one(ctx, sexp_car(obj), out, bound+1);
      x = sexp_cdr(obj);
      for (x2=sexp_pairp(x)?sexp_cdr(x):SEXP_NULL; sexp_pairp(x); x=sexp_cdr(x), x2=(sexp_pairp(x2)&&sexp_pairp(sexp_cdr(x2))?sexp_cddr(x2):SEXP_NULL)) {
        if (x == x2) {
          sexp_write_string(ctx, "...", out);
          return SEXP_VOID;
        }
        sexp_write_char(ctx, ' ', out);
        sexp_write_one(ctx, sexp_car(x), out, bound+1);
      }
      if (! sexp_nullp(x)) {
        sexp_write_string(ctx, " . ", out);
        sexp_write_one(ctx, x, out, bound+1);
      }
      sexp_write_char(ctx, ')', out);
      break;
    case SEXP_VECTOR:
      len = sexp_vector_length(obj);
      elts = sexp_vector_data(obj);
      if (len == 0) {
        sexp_write_string(ctx, "#()", out);
      } else {
        sexp_write_string(ctx, "#(", out);
        sexp_write_one(ctx, elts[0], out, bound+1);
        for (i=1; i<(sexp_sint_t)len; i++) {
          sexp_write_char(ctx, ' ', out);
          sexp_write_one(ctx, elts[i], out, bound+1);
        }
        sexp_write_char(ctx, ')', out);
      }
      break;
#if SEXP_USE_FLONUMS
#if ! SEXP_USE_IMMEDIATE_FLONUMS
    case SEXP_FLONUM:
      f = sexp_flonum_value(obj);
#if SEXP_USE_INFINITIES
      if (isinf(f) || isnan(f)) {
        numbuf[0] = (isinf(f) && f < 0 ? '-' : '+');
        strncpy(numbuf+1, isinf(f) ? "inf.0" : "nan.0", NUMBUF_LEN-1);
      } else
#endif
      {
        /* snprintf doesn't guarantee the shortest accurate */
        /* representation, so we try successively longer formats until */
        /* we find the one that scans back as the original number */
        i = snprintf(numbuf, sizeof(numbuf), "%.15lg", f);
        if (sscanf(numbuf, "%lg", &ftmp) == 1 && ftmp != f) {
          i = snprintf(numbuf, sizeof(numbuf), "%.16lg", f);
          if (sscanf(numbuf, "%lg", &ftmp) == 1 && ftmp != f) {
            i = snprintf(numbuf, sizeof(numbuf), "%.17lg", f);
          }
        }
        for (j = 0; j < i; ++j) {
          if (numbuf[j] == '.' || numbuf[j] == 'e') {
            break;
#if SEXP_USE_PATCH_NON_DECIMAL_NUMERIC_FORMATS
          } else if (!sexp_isdigit(numbuf[j]) && numbuf[j] != '-') {
            /* handle the case where we're embedded in an app which has */
            /* called setlocale to something which doesn't use a decimal */
            /* separator (e.g. a comma), by replacing any */
            /* non-digit/decimal char with a decimal */
            for (k = j+1; k < i && !sexp_isdigit(numbuf[k]); ++k)
              ;
            numbuf[j++] = '.';
            while (k < i)
              numbuf[j++] = numbuf[k++];
            numbuf[j++] = '\0';
            j = 0;
            break;
          }
#endif
        }
        /* regardless, append a decimal if there wasn't any */
        if (j >= i) {
          numbuf[i++] = '.'; numbuf[i++] = '0'; numbuf[i++] = '\0';
        }
      }
      sexp_write_string(ctx, numbuf, out);
      break;
#endif
#endif
    case SEXP_PROCEDURE:
      sexp_write_string(ctx, "#<procedure ", out);
      if (sexp_procedure_variable_transformer_p(obj))
        sexp_write_string(ctx, "(variable-transformer) ", out);
      x = sexp_bytecode_name(sexp_procedure_code(obj));
      sexp_write_one(ctx, sexp_synclop(x) ? sexp_synclo_expr(x): x, out, bound+1);
      sexp_write_string(ctx, " ", out);
      sexp_write_one(ctx, sexp_make_fixnum(sexp_procedure_num_args(obj)), out, bound+1);
      if (sexp_procedure_variadic_p(obj)) {
        sexp_write_string(ctx, "+", out);
      }
#if SEXP_USE_DEBUG_VM
      if (sexp_procedure_source(obj)) {
        sexp_write_string(ctx, " ", out);
        sexp_write(ctx, sexp_procedure_source(obj), out);
      }
#endif
      sexp_write_string(ctx, ">", out);
      break;
    case SEXP_TYPE:
      sexp_write_string(ctx, "#<type ", out);
      sexp_write(ctx, sexp_type_name(obj), out);
      sexp_write_string(ctx, ">", out);
      break;
#if 0
    case SEXP_ENV:
      sexp_write_string(ctx, "#<Env ", out);
      sexp_write(ctx, sexp_make_fixnum(obj), out);
      sexp_write_string(ctx, " ", out);
      sexp_write(ctx, sexp_make_fixnum(sexp_env_bindings(obj)), out);
      sexp_write_string(ctx, " (", out);
      sexp_write(ctx, sexp_length(ctx, sexp_env_bindings(obj)), out);
      sexp_write_string(ctx, ")", out);
      if (sexp_env_parent(obj)) {
        sexp_write_string(ctx, " ", out);
        sexp_write(ctx, sexp_env_parent(obj), out);
      }
      sexp_write_string(ctx, ">", out);
      break;
#endif
    case SEXP_STRING:
      sexp_write_char(ctx, '"', out);
      i = sexp_string_size(obj);
      str = sexp_string_data(obj);
      for ( ; i>0; str++, i--) {
        switch (str[0]) {
        case '\\': sexp_write_string(ctx, "\\\\", out); break;
        case '"': sexp_write_string(ctx, "\\\"", out); break;
        case '\a': sexp_write_string(ctx, "\\a", out); break;
        case '\b': sexp_write_string(ctx, "\\b", out); break;
        case '\n': sexp_write_string(ctx, "\\n", out); break;
        case '\r': sexp_write_string(ctx, "\\r", out); break;
        case '\t': sexp_write_string(ctx, "\\t", out); break;
        default:
          if (str[0] < ' ' && str[0] >= 0) {
            sexp_write_string(ctx, "\\x", out);
            sexp_write_char(ctx, hex_digit(str[0]>>4), out);
            sexp_write_char(ctx, hex_digit(str[0]&0x0F), out);
            sexp_write_char(ctx, ';', out);
          } else {
            sexp_write_char(ctx, str[0], out);
          }
        }
      }
      sexp_write_char(ctx, '"', out);
      break;
    case SEXP_SYMBOL:
      str = sexp_lsymbol_data(obj);
      c = (sexp_lsymbol_length(obj) == 0 ||
           (sexp_lsymbol_length(obj) == 1 && str[0] == '.') ||
           sexp_isdigit((unsigned char)str[0]) ||
           (sexp_lsymbol_length(obj) > 1 &&
            ((str[0] == '+' || str[0] == '-')
             && (sexp_isdigit((unsigned char)str[1]) ||
                 str[1] == '.' || str[1] == 'i' ||
                 ((sexp_lsymbol_length(obj) > 3) &&
                  sexp_tolower((unsigned char)str[1]) == 'n' &&
                  sexp_tolower((unsigned char)str[2]) == 'a' &&
                  sexp_tolower((unsigned char)str[3]) == 'n')))))
        ? '|' : EOF;
      for (i=sexp_lsymbol_length(obj)-1; i>=0; i--)
        if (str[i] <= ' ' || str[i] == '\\' || str[i] == '|' || str[i] == '#' || sexp_is_separator(str[i]))
          c = '|';
      if (c!=EOF) sexp_write_char(ctx, c, out);
      for (i=sexp_lsymbol_length(obj); i>0; str++, i--) {
        if (str[0] == '\\' || str[0] == '|') sexp_write_char(ctx, '\\', out);
        sexp_write_char(ctx, str[0], out);
      }
      if (c!=EOF) sexp_write_char(ctx, c, out);
      break;
#if SEXP_USE_BIGNUMS
    case SEXP_BIGNUM:
      sexp_write_bignum(ctx, obj, out, 10);
      break;
#endif
#if SEXP_USE_RATIOS
    case SEXP_RATIO:
      sexp_write(ctx, sexp_ratio_numerator(obj), out);
      sexp_write_char(ctx, '/', out);
      sexp_write(ctx, sexp_ratio_denominator(obj), out);
      break;
#endif
#if SEXP_USE_COMPLEX
    case SEXP_COMPLEX:
      sexp_write(ctx, sexp_complex_real(obj), out);
      if (!sexp_pedantic_negativep(sexp_complex_imag(obj))
          && !sexp_infp(sexp_complex_imag(obj)))
        sexp_write_char(ctx, '+', out);
      if (sexp_complex_imag(obj) == SEXP_NEG_ONE)
        sexp_write_char(ctx, '-', out);
      else if (sexp_complex_imag(obj) != SEXP_ONE)
        sexp_write(ctx, sexp_complex_imag(obj), out);
      sexp_write_char(ctx, 'i', out);
      break;
#endif
    case SEXP_OPCODE:
      sexp_write_string(ctx, "#<opcode ", out);
      sexp_write(ctx, sexp_opcode_name(obj), out);
      sexp_write_char(ctx, '>', out);
      break;
#if SEXP_USE_BYTEVECTOR_LITERALS
    case SEXP_BYTES:
      sexp_write_string(ctx, "#u8(", out);
      str = sexp_bytes_data(obj);
      len = sexp_bytes_length(obj);
      for (i=0; i<(sexp_sint_t)len; i++) {
        if (i!=0) sexp_write_char(ctx, ' ', out);
#if SEXP_BYTEVECTOR_HEX_LITERALS
	if (str[i]) {
          snprintf(buf, sizeof(buf), "#x%02hhX", ((unsigned char*) str)[i]);
	  sexp_write_string(ctx, buf, out);
	} else {
	  sexp_write_char (ctx, '0', out);
	}
#else
        sexp_write(ctx, sexp_make_fixnum(((unsigned char*)str)[i]), out);
#endif
      }
      sexp_write_char(ctx, ')', out);
      break;
#endif
    case SEXP_FILENO:
      sexp_write_string(ctx, "#<fileno ", out);
      sexp_write(ctx, sexp_make_fixnum(sexp_fileno_fd(obj)), out);
      sexp_write_char(ctx, '>', out);
      break;
    case SEXP_SYNCLO:
      sexp_write_string(ctx, "#<SC ", out);
      sexp_write(ctx, sexp_make_fixnum(obj), out);
      sexp_write_char(ctx, ' ', out);
      sexp_write(ctx, sexp_synclo_expr(obj), out);
      sexp_write_char(ctx, ' ', out);
      sexp_write(ctx, sexp_synclo_rename(obj), out);
      sexp_write_char(ctx, '>', out);
      break;
    default:
      i = sexp_pointer_tag(obj);
      if (i < 0 || i >= sexp_context_num_types(ctx)) {
        sexp_write_string(ctx, "#<invalid type tag: ", out);
        sexp_write(ctx, sexp_make_fixnum(i), out);
        sexp_write_char(ctx, '>', out);
      } else {
        x = sexp_type_by_index(ctx, i);
#if 0 && SEXP_USE_TYPE_PRINTERS
        if (sexp_type_print(x)) {
          x = sexp_apply3(ctx, sexp_type_print(x), obj, SEXP_FALSE, out);
          if (sexp_exceptionp(x)) return x;
        } else {
#endif
          sexp_write_string(ctx, "#<", out);
          if (sexp_stringp(sexp_type_name(x)))
            sexp_write_string(ctx, sexp_string_data(sexp_type_name(x)), out);
          else
            sexp_write(ctx, sexp_type_name(x), out);
          sexp_write_char(ctx, ' ', out);
          sexp_write(ctx, sexp_make_fixnum(obj), out);
          sexp_write_char(ctx, '>', out);
#if 0 && SEXP_USE_TYPE_PRINTERS
        }
#endif
      }
      break;
    }
  } else if (sexp_fixnump(obj)) {
    snprintf(numbuf, sizeof(numbuf), "%" SEXP_PRIdFIXNUM, (sexp_sint_t)sexp_unbox_fixnum(obj));
    sexp_write_string(ctx, numbuf, out);
#if SEXP_USE_IMMEDIATE_FLONUMS
  } else if (sexp_flonump(obj)) {
    f = sexp_flonum_value(obj);
#if SEXP_USE_INFINITIES
    if (isinf(f) || isnan(f)) {
      numbuf[0] = (isinf(f) && f < 0 ? '-' : '+');
      strncpy(numbuf+1, isinf(f) ? "inf.0" : "nan.0", NUMBUF_LEN-1);
    } else
#endif
    {
      i = snprintf(numbuf, sizeof(numbuf), "%.8g", f);
      if (f == trunc(f) && ! strchr(numbuf, '.')) {
        numbuf[i++] = '.'; numbuf[i++] = '0'; numbuf[i++] = '\0';
      }
    }
    sexp_write_string(ctx, numbuf, out);
#endif
  } else if (sexp_string_cursorp(obj)) {
    sexp_write_string(ctx, "{String-Cursor #", out);
    sexp_write(ctx, sexp_make_fixnum(SEXP_STRING_CURSOR), out);
    sexp_write_char(ctx, ' ', out);
    sexp_write(ctx, sexp_make_fixnum(sexp_unbox_string_cursor(obj)), out);
    sexp_write_char(ctx, '}', out);
  } else if (sexp_charp(obj)) {
    sexp_write_string(ctx, "#\\", out);
    for (i=0; i < sexp_num_char_names; i++) {
      if (sexp_unbox_character(obj) == sexp_char_names[i].ch) {
        sexp_write_string(ctx, sexp_char_names[i].name, out);
        break;
      }
    }
    if (i >= sexp_num_char_names) {
      if ((33 <= sexp_unbox_character(obj))
          && (sexp_unbox_character(obj) < 127)) {
        sexp_write_char(ctx, sexp_unbox_character(obj), out);
      } else {
        sexp_write_string(ctx, "x", out);
        c = sexp_unbox_character(obj);
        if (c >= 0x100) {
          if (c >= 0x10000) {
            sexp_write_char(ctx, hex_digit((c>>20)&0x0F), out);
            sexp_write_char(ctx, hex_digit((c>>16)&0x0F), out);
          }
          sexp_write_char(ctx, hex_digit((c>>12)&0x0F), out);
          sexp_write_char(ctx, hex_digit((c>>8)&0x0F), out);
        }
        sexp_write_char(ctx, hex_digit((c>>4)&0x0F), out);
        sexp_write_char(ctx, hex_digit(c&0x0F), out);
      }
    }
#if SEXP_USE_HUFF_SYMS
  } else if (sexp_isymbolp(obj)) {
    if (sexp_isymbolp(obj)) {
      c = ((sexp_uint_t)obj)>>SEXP_IMMEDIATE_BITS;
      while (c) {
#include "chibi/sexp-unhuff.h"
        sexp_write_char(ctx, res, out);
      }
    }
#endif
  } else {
    switch ((sexp_uint_t) obj) {
    case (sexp_uint_t) SEXP_NULL:
      sexp_write_string(ctx, "()", out); break;
    case (sexp_uint_t) SEXP_TRUE:
      sexp_write_string(ctx, "#t", out); break;
    case (sexp_uint_t) SEXP_FALSE:
      sexp_write_string(ctx, "#f", out); break;
    case (sexp_uint_t) SEXP_EOF:
      sexp_write_string(ctx, "#<eof>", out); break;
    case (sexp_uint_t) SEXP_UNDEF:
    case (sexp_uint_t) SEXP_VOID:
      sexp_write_string(ctx, "#<undef>", out); break;
    default:
      sexp_write_string(ctx, "#<invalid immediate: ", out);
      sexp_write(ctx, sexp_make_fixnum(obj), out);
      sexp_write_char(ctx, '>', out);
    }
  }
  return SEXP_VOID;
}

sexp sexp_write_op (sexp ctx, sexp self, sexp_sint_t n, sexp obj, sexp out) {
  sexp res;
  sexp_assert_type(ctx, sexp_oportp, SEXP_OPORT, out);
#if SEXP_USE_GREEN_THREADS
  sexp_maybe_block_output_port(ctx, out);
#endif
  res = sexp_write_one(ctx, obj, out, 0);
#if SEXP_USE_GREEN_THREADS
  sexp_maybe_unblock_port(ctx, out);
#endif
  return res;
}

#if SEXP_USE_UTF8_STRINGS
int sexp_write_utf8_char (sexp ctx, int c, sexp out) {
  unsigned char buf[8];
  int len = sexp_utf8_char_byte_count(c), i;
  sexp_utf8_encode_char(buf, len, c);
  buf[len] = 0;
  i = sexp_write_char(ctx, buf[0], out);
  if (i == EOF) return EOF;
  sexp_write_string(ctx, (char*)buf+1, out);
  return len;
}
#endif

sexp sexp_flush_output_op (sexp ctx, sexp self, sexp_sint_t n, sexp out) {
  int res;
  sexp_assert_type(ctx, sexp_oportp, SEXP_OPORT, out);
  res = sexp_flush_forced(ctx, out);
  if (res == EOF) {
#if SEXP_USE_GREEN_THREADS
    if (sexp_port_stream(out) && ferror(sexp_port_stream(out)) && (errno == EAGAIN))
      return sexp_global(ctx, SEXP_G_IO_BLOCK_ERROR);
#endif
    return SEXP_FALSE;
  }
  return SEXP_TRUE;
}

#define INIT_STRING_BUFFER_SIZE 128

sexp sexp_read_string (sexp ctx, sexp in, int sentinel) {
#if SEXP_USE_UTF8_STRINGS
  int len;
#endif
  int c, i=0;
  sexp_sint_t size=INIT_STRING_BUFFER_SIZE;
  char initbuf[INIT_STRING_BUFFER_SIZE];
  char *buf=initbuf, *tmp;
  sexp res = SEXP_FALSE;

  for (c = sexp_read_char(ctx, in); c != sentinel; c = sexp_read_char(ctx, in)) {
    if (c == '\\') {
      c = sexp_read_char(ctx, in);
      switch (c) {
      case 'a': c = '\a'; break;
      case 'b': c = '\b'; break;
      case 'n': c = '\n'; break;
      case 'r': c = '\r'; break;
      case 't': c = '\t'; break;
      case 'x': case 'X':
        res = sexp_read_number(ctx, in, 16, 0);
        if (sexp_fixnump(res)) {
          c = sexp_read_char(ctx, in);
          if (c != ';') {
#if SEXP_USE_ESCAPE_REQUIRES_TRAILING_SEMI_COLON
            res = sexp_read_error(ctx, "missing ; in \\x escape", SEXP_NULL, in);
#else
            sexp_push_char(ctx, c, in);
#endif
          }
          c = sexp_unbox_fixnum(res);
#if SEXP_USE_UTF8_STRINGS
          if ((unsigned)c > 0x80) {
            len = sexp_utf8_char_byte_count(c);
            sexp_utf8_encode_char((unsigned char*)buf + i, len, c);
            i += len;
            goto maybe_expand;
          }
#endif
        }
        break;
#if SEXP_USE_ESCAPE_NEWLINE
      default:
        if (isspace(c)) {
          while (c==' ' || c=='\t') c=sexp_read_char(ctx, in);
          if (c=='\r') c=sexp_read_char(ctx, in);
          if (c=='\n') {
            sexp_port_line(in)++;
            do {c=sexp_read_char(ctx, in);} while (c==' ' || c=='\t');
            sexp_push_char(ctx, c, in);
            continue;
          }
        }
#endif
      }
      if (sexp_exceptionp(res)) break;
    } else if (c == '\n') {
      sexp_port_line(in)++;
    } else if (c == EOF) {
      res = sexp_read_incomplete_error(ctx, "premature end of string", SEXP_NULL, in);
      break;
    }
    buf[i++] = c;
  maybe_expand:
    if (i+4 >= size) {       /* expand buffer w/ malloc(), later free() it */
      tmp = (char*) sexp_malloc(size*2);
      if (!tmp) {res = sexp_global(ctx, SEXP_G_OOM_ERROR); break;}
      memcpy(tmp, buf, i);
      if (size != INIT_STRING_BUFFER_SIZE) free(buf);
      buf = tmp;
      size *= 2;
    }
  }

  if (!sexp_exceptionp(res)) {
    buf[i] = '\0';
    res = sexp_c_string(ctx, buf, i);
  }
  if (size != INIT_STRING_BUFFER_SIZE) free(buf);
  return res;
}

sexp sexp_read_symbol (sexp ctx, sexp in, int init, int internp) {
  int c, i=0, size=INIT_STRING_BUFFER_SIZE;
  char initbuf[INIT_STRING_BUFFER_SIZE];
  char *buf=initbuf, *tmp;
  sexp res=SEXP_VOID;
#if SEXP_USE_FOLD_CASE_SYMS
  int foldp = sexp_port_fold_casep(in);
  init = (foldp ? sexp_tolower(init) : init);
#endif

  if (init != EOF)
    buf[i++] = init;

  for (c = sexp_read_char(ctx, in); ; c = sexp_read_char(ctx, in)) {
#if SEXP_USE_FOLD_CASE_SYMS
    if (foldp) c = sexp_tolower(c);
#endif
    if (c == '\\') c = sexp_read_char(ctx, in);
    if (c == EOF || sexp_is_separator(c)) {
      sexp_push_char(ctx, c, in);
      break;
    }
    buf[i++] = c;
    if (i >= size) {       /* expand buffer w/ malloc(), later free() it */
      tmp = (char*) sexp_malloc(size*2);
      if (!tmp) {res = sexp_global(ctx, SEXP_G_OOM_ERROR); break;}
      memcpy(tmp, buf, i);
      if (size != INIT_STRING_BUFFER_SIZE) free(buf);
      buf = tmp;
      size *= 2;
    }
  }

  if (!sexp_exceptionp(res)) {
    buf[i] = '\0';
    res = (internp ? sexp_intern(ctx, buf, i) : sexp_c_string(ctx, buf, i));
  }
  if (size != INIT_STRING_BUFFER_SIZE) free(buf);
  return res;
}

#if SEXP_USE_COMPLEX
sexp sexp_make_complex (sexp ctx, sexp real, sexp image) {
  sexp res = sexp_alloc_type(ctx, complex, SEXP_COMPLEX);
  sexp_complex_real(res) = real;
  sexp_complex_imag(res) = image;
  return res;
}

sexp sexp_complex_normalize (sexp cpx) {
  return sexp_complexp(cpx)
    && (sexp_complex_imag(cpx) == SEXP_ZERO
        /* Only normalize for an exact zero imaginary part.  */
        /* || (sexp_flonump(sexp_complex_imag(cpx)) */
        /*     && sexp_flonum_value(sexp_complex_imag(cpx)) == 0.0) */
        )
    ? sexp_complex_real(cpx) : cpx;
}

sexp sexp_read_complex_tail (sexp ctx, sexp in, sexp real) {
  int c = sexp_read_char(ctx, in), c2;
  sexp default_real = SEXP_ZERO;
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  res = SEXP_VOID;
  if (c=='i' || c=='I') {       /* trailing i, no sign */
  trailing_i:
    c = sexp_read_char(ctx, in);
    if (c=='n' || c=='N') {
      res = sexp_read_symbol(ctx, in, c, 1);
      if (res == sexp_intern(ctx, "nf.0i", -1))
        real = res = sexp_make_flonum(ctx, real == SEXP_ONE ? sexp_pos_infinity : sexp_neg_infinity);
      else
        goto invalid;
    } else if ((c!=EOF) && ! sexp_is_separator(c)) {
    invalid:
      res = sexp_read_error(ctx, "invalid complex numeric syntax", sexp_make_character(c), in);
    } else
      sexp_push_char(ctx, c, in);
    if (!sexp_exceptionp(res))
      res = sexp_make_complex(ctx, default_real, real);  /* NNNNi has 0 real */
  } else {                      /* trailing + or - */
    c2 = sexp_read_char(ctx, in);
    if (c2=='i' || c2=='I') {
      default_real = real;
      real = (c=='-') ? SEXP_NEG_ONE : SEXP_ONE;
      goto trailing_i;
    } else {
      sexp_push_char(ctx, c2, in);
      /* read imaginary part */
      if (c=='-') sexp_push_char(ctx, c, in);
      res = sexp_read_number(ctx, in, 10, 0);
      if (sexp_complexp(res)) {
        if (sexp_complex_real(res) == SEXP_ZERO)
          sexp_complex_real(res) = real;
        else
          res = sexp_read_error(ctx, "multiple real parts of complex", res, in);
      } else if ((res == SEXP_ZERO)
                 || (sexp_flonump(res) && sexp_flonum_value(res) == 0.0)) {
        res = sexp_make_complex(ctx, real, res);
      } else {                    /* found trailing +/-NNNN with no i */
        res = sexp_exceptionp(res) ? res
          : sexp_read_error(ctx, "missing imaginary part of complex", res, in);
      }
    }
  }
  sexp_gc_release1(ctx);
  return sexp_complex_normalize(res);
}

#if SEXP_USE_MATH
sexp sexp_read_polar_tail (sexp ctx, sexp in, sexp magnitude) {
  sexp_gc_var2(res, theta);
  sexp_gc_preserve2(ctx, res, theta);
  theta = sexp_read_number(ctx, in, 10, 0);
  if (sexp_exceptionp(theta)) {
    res = theta;
  } else if (sexp_complexp(theta) || !sexp_numberp(theta)) {
    res = sexp_read_error(ctx, "invalid polar numeric syntax", theta, in);
  } else if (theta == SEXP_ZERO) {
    res = magnitude;
  } else {
    res = sexp_make_complex(ctx, SEXP_ZERO, SEXP_ZERO);
    sexp_complex_real(res) = sexp_cos(ctx, NULL, 1, theta);
    sexp_complex_real(res) = sexp_mul(ctx, magnitude, sexp_complex_real(res));
    sexp_complex_imag(res) = sexp_sin(ctx, NULL, 1, theta);
    sexp_complex_imag(res) = sexp_mul(ctx, magnitude, sexp_complex_imag(res));
    if (sexp_exceptionp(sexp_complex_real(res))) res = sexp_complex_real(res);
    if (sexp_exceptionp(sexp_complex_imag(res))) res = sexp_complex_imag(res);
  }
  sexp_gc_release2(ctx);
  return sexp_complex_normalize(res);
}
#endif
#endif

sexp sexp_read_float_tail (sexp ctx, sexp in, double whole, int negp) {
  int c, c2;
  sexp exponent=SEXP_VOID;
  long double val=0.0, scale=10, e=0.0;
  sexp_gc_var1(res);
  sexp_gc_preserve1(ctx, res);
  for (c=sexp_read_char(ctx, in); sexp_isdigit(c);
       c=sexp_read_char(ctx, in), val*=10, scale*=10)
    val += digit_value(c);
#if SEXP_USE_PLACEHOLDER_DIGITS
  for (; c==SEXP_PLACEHOLDER_DIGIT;
       c=sexp_read_char(ctx, in), val*=10, scale*=10)
    val += sexp_placeholder_digit_value(10);
#endif
  val /= scale;
  val += whole;
  if (negp) val *= -1;
  if (is_precision_indicator(c)) {
    c2 = sexp_read_char(ctx, in);
    if (c2 != '+') sexp_push_char(ctx, c2, in);
    exponent = sexp_read_number(ctx, in, 10, 0);
    if (sexp_exceptionp(exponent)) {
      sexp_gc_release1(ctx);
      return exponent;
    }
#if SEXP_USE_COMPLEX
    if (sexp_complexp(exponent)) {
      res = exponent;
      exponent = (sexp_complex_real(res) == SEXP_ZERO ? sexp_complex_imag(res) : sexp_complex_real(res));
    }
#endif
    e = (sexp_fixnump(exponent) ? sexp_unbox_fixnum(exponent)
         : sexp_flonump(exponent) ? sexp_flonum_value(exponent) : 0.0);
#if SEXP_USE_COMPLEX
    if (sexp_complexp(res)) {
      if (sexp_complex_real(res) == SEXP_ZERO) {
        sexp_complex_imag(res) = sexp_make_flonum(ctx, val * pow(10, e));
      } else {
        sexp_complex_real(res) = sexp_make_flonum(ctx, val * pow(10, e));
      }
      sexp_gc_release1(ctx);
      return res;
    }
#endif
  }
  if (e != 0.0)
    val = fabsl(e) > 320 ? exp(log(val) + e*M_LN10) : val * pow(10, e);
#if SEXP_USE_FLONUMS
  res = sexp_make_flonum(ctx, val);
#else
  res = sexp_make_fixnum((sexp_uint_t)val);
#endif
  if (!is_precision_indicator(c)) {
#if SEXP_USE_COMPLEX
    if (c=='i' || c=='I' || c=='+' || c=='-') {
      sexp_push_char(ctx, c, in);
      res = sexp_read_complex_tail(ctx, in, res);
#if SEXP_USE_MATH
    } else if (c=='@') {
      return sexp_read_polar_tail(ctx, in, res);
#endif
    } else
#endif
    if ((c!=EOF) && ! sexp_is_separator(c))
      res = sexp_read_error(ctx, "invalid numeric syntax",
                            sexp_make_character(c), in);
    else
      sexp_push_char(ctx, c, in);
  }
  sexp_gc_release1(ctx);
  return res;
}

#if SEXP_USE_RATIOS
sexp sexp_make_ratio (sexp ctx, sexp num, sexp den) {
  sexp res = sexp_alloc_type(ctx, ratio, SEXP_RATIO);
  sexp_ratio_numerator(res) = num;
  sexp_ratio_denominator(res) = den;
  return res;
}

sexp sexp_ratio_normalize (sexp ctx, sexp rat, sexp in) {
  sexp tmp;
  sexp_gc_var2(num, den);
  if (sexp_exact_negativep(sexp_ratio_denominator(rat))) {
    /* Prevent overflow in the sexp_negate. */
    if (sexp_ratio_numerator(rat) == sexp_make_fixnum(SEXP_MIN_FIXNUM))
      sexp_ratio_numerator(rat) = sexp_fixnum_to_bignum(ctx, sexp_ratio_numerator(rat));
    sexp_negate(sexp_ratio_numerator(rat));
    sexp_negate(sexp_ratio_denominator(rat));
  }
  num = sexp_ratio_numerator(rat), den = sexp_ratio_denominator(rat);
  if (den == SEXP_ZERO)
    return sexp_read_error(ctx, "zero denominator in ratio", rat, in);
  else if (num == SEXP_ZERO)
    return SEXP_ZERO;
  sexp_gc_preserve2(ctx, num, den);
  while (den != SEXP_ZERO) {
    tmp = sexp_remainder(ctx, num, den);
    if (sexp_exceptionp(tmp)) {
      sexp_gc_release2(ctx);
      return tmp;
    }
    num = den, den = tmp;
  }
  sexp_ratio_denominator(rat)
    = den = sexp_quotient(ctx, sexp_ratio_denominator(rat), num);
  sexp_ratio_numerator(rat)
    = sexp_quotient(ctx, sexp_ratio_numerator(rat), num);
  if (sexp_exact_negativep(sexp_ratio_denominator(rat))) {
    /* Prevent overflow in the sexp_negate. */
    if (sexp_ratio_numerator(rat) == sexp_make_fixnum(SEXP_MIN_FIXNUM))
      sexp_ratio_numerator(rat) = sexp_fixnum_to_bignum(ctx, sexp_ratio_numerator(rat));
    sexp_negate(sexp_ratio_numerator(rat));
    sexp_negate(sexp_ratio_denominator(rat));
  }
  sexp_ratio_numerator(rat) = sexp_bignum_normalize(sexp_ratio_numerator(rat));
  sexp_ratio_denominator(rat) = sexp_bignum_normalize(sexp_ratio_denominator(rat));
  sexp_gc_release2(ctx);
  return (sexp_ratio_denominator(rat) == SEXP_ONE) ? sexp_ratio_numerator(rat)
    : rat;
}
#endif

sexp sexp_read_number (sexp ctx, sexp in, int base, int exactp) {
  sexp_sint_t val = 0, tmp = -1;
  int c, digit, negativep = 0, inexactp = 0;
#if SEXP_USE_PLACEHOLDER_DIGITS
  double whole = 0.0, scale = 0.1;
#endif
#if SEXP_USE_COMPLEX && SEXP_USE_MATH
  double rho, theta;
#endif
  sexp_gc_var2(res, den);

  c = sexp_read_char(ctx, in);
  if (c == '#') {
    switch ((c = sexp_tolower(sexp_read_char(ctx, in)))) {
      case 'b': base = 2; break;   case 'o': base = 8; break;
      case 'd': base = 10; break;  case 'x': base = 16; break;
      case 'i': inexactp = 1; break; case 'e': exactp = 1; break;
      default: return sexp_read_error(ctx, "unexpected numeric # code", sexp_make_character(c), in);
    }
    c = sexp_read_char(ctx, in);
  }
  if (c == '-') {
    negativep = 1;
    c = sexp_read_char(ctx, in);
  } else if (c == '+') {
    c = sexp_read_char(ctx, in);
  }

#if SEXP_USE_COMPLEX
  if (c == 'i' || c == 'I') val = 1;
#endif

  for ( ; sexp_isxdigit(c); c=sexp_read_char(ctx, in)) {
    digit = digit_value(c);
    if ((digit < 0) || (digit >= base))
      break;
    tmp = val * base + digit;
#if SEXP_USE_BIGNUMS
    if ((SEXP_MAX_FIXNUM / base < val) ||
        (tmp < val) || (tmp > SEXP_MAX_FIXNUM)) {
      sexp_push_char(ctx, c, in);
      return sexp_read_bignum(ctx, in, val, (negativep ? -1 : 1), base);
    }
#endif
    val = tmp;
  }

#if SEXP_USE_PLACEHOLDER_DIGITS
  if (sexp_placeholder_digit_p(c) && tmp >= 0) {
    whole = val;
    for ( ; sexp_placeholder_digit_p(c); c=sexp_read_char(ctx, in))
      whole = whole*10 + sexp_placeholder_digit_value(base);
    if ((c=='.' || is_precision_indicator(c)) && (base != 10))
      return sexp_read_error(ctx, "found non-base 10 float", SEXP_NULL, in);
    if (c=='.')
      for (c=sexp_read_char(ctx, in); sexp_placeholder_digit_p(c);
           c=sexp_read_char(ctx, in), scale*=0.1)
        whole += sexp_placeholder_digit_value(10)*scale;
    if (is_precision_indicator(c)) {
      sexp_push_char(ctx, c, in);
      return sexp_read_float_tail(ctx, in, whole, negativep);
    } else if ((c!=EOF) && !sexp_is_separator(c)) {
      return sexp_read_error(ctx, "invalid numeric syntax after placeholders",
                             sexp_make_character(c), in);
    }
    sexp_push_char(ctx, c, in);
    return sexp_make_flonum(ctx, (negativep ? -whole : whole));
  }
#endif

  if (exactp && is_precision_indicator(c)) {
    sexp_gc_preserve2(ctx, res, den);
    res = sexp_make_fixnum(negativep ? -val : val);
    den = sexp_read_number(ctx, in, base, 0);
    if (sexp_exceptionp(den)) {
      res = den;
    } else {
      if (sexp_flonump(den)) den = sexp_make_fixnum(sexp_flonum_value(den));
      if (sexp_complexp(den)) {
        if (sexp_flonump(sexp_complex_real(den)))
          sexp_complex_real(den) = sexp_make_fixnum(sexp_flonum_value(sexp_complex_real(den)));
        sexp_complex_real(den) = sexp_expt(ctx, SEXP_TEN, sexp_complex_real(den));
        sexp_complex_real(den) = sexp_mul(ctx, res, sexp_complex_real(den));
        res = den;
      } else {
        den = sexp_expt(ctx, SEXP_TEN, den);
        res = sexp_mul(ctx, res, den);
      }
    }
    sexp_gc_release2(ctx);
    return res;
  } else if (c=='.' || is_precision_indicator(c)) {
    if (base != 10)
      return sexp_read_error(ctx, "found non-base 10 float", SEXP_NULL, in);
    if (c!='.') sexp_push_char(ctx, c, in);
    return sexp_read_float_tail(ctx, in, val, negativep);
  } else if (c=='/') {
    sexp_gc_preserve2(ctx, res, den);
    den = sexp_read_number(ctx, in, base, exactp);
    if (! (sexp_fixnump(den) || sexp_bignump(den) ||
           (sexp_complexp(den) && sexp_exactp(sexp_complex_real(den)) && sexp_exactp(sexp_complex_imag(den)))))
      res = (sexp_exceptionp(den)
             ? den : sexp_read_error(ctx, "invalid rational syntax", den, in));
    else {
#if SEXP_USE_RATIOS
#if SEXP_USE_COMPLEX
    if (sexp_complexp(den)) {
      res = sexp_make_fixnum(negativep ? -val : val);
      if (sexp_complex_real(den) == SEXP_ZERO) {
        res = sexp_make_ratio(ctx, res, sexp_complex_imag(den));
        res = sexp_ratio_normalize(ctx, res, in);
        sexp_complex_imag(den) = res;
#if SEXP_USE_MATH
      } else if (sexp_flonump(sexp_complex_real(den))) { /* assume polar */
        double real = sexp_flonum_value(sexp_complex_real(den));
        rho = sqrt(real * real +
                   sexp_to_double(ctx, sexp_complex_imag(den)) *
                   sexp_to_double(ctx, sexp_complex_imag(den)));
        theta = atan(sexp_to_double(ctx, sexp_complex_imag(den)) /
                     sexp_flonum_value(sexp_complex_real(den)));
        if (real < 0) {
          theta += M_PI;
        }
        rho = sexp_to_double(ctx, sexp_div(ctx, res, sexp_make_fixnum((sexp_sint_t)round(rho))));
        sexp_complex_real(den) = sexp_make_flonum(ctx, rho * cos(theta));
        sexp_complex_imag(den) = sexp_make_flonum(ctx, rho * sin(theta));
#endif
      } else {
        res = sexp_make_ratio(ctx, res, sexp_complex_real(den));
        res = sexp_ratio_normalize(ctx, res, in);
        sexp_complex_real(den) = res;
      }
      if (!sexp_exceptionp(res))
        res = den;
    } else
#endif
      do {
    res = sexp_make_ratio(ctx, sexp_make_fixnum(negativep ? -val : val), den);
    res = sexp_ratio_normalize(ctx, res, in);
      } while (0);
#else
    if (!sexp_exceptionp(res))
      res = sexp_make_flonum(ctx, (double)(negativep ? -val : val)
                             / (double)sexp_unbox_fixnum(den));
#endif
    }
    if (inexactp)
      res = sexp_exact_to_inexact(ctx, NULL, 2, res);
    sexp_gc_release2(ctx);
    return res;
#if SEXP_USE_COMPLEX
  } else if (c=='i' || c=='I' || c=='+' || c=='-' || c=='@') {
    if (base != 10)
      return sexp_read_error(ctx, "found non-base 10 complex", SEXP_NULL, in);
#if SEXP_USE_MATH
    if (c=='@')
      return sexp_read_polar_tail(ctx, in, sexp_make_fixnum(negativep ? -val : val));
#endif
    sexp_push_char(ctx, c, in);
    return sexp_read_complex_tail(ctx, in, sexp_make_fixnum(negativep ? -val : val));
#endif
  } else {
    if ((c!=EOF) && ! sexp_is_separator(c))
      return sexp_read_error(ctx, "invalid numeric syntax",
                             sexp_make_character(c), in);
    else if (tmp < 0)
      return sexp_read_error(ctx, "digitless numeric literal", SEXP_NULL, in);
    sexp_push_char(ctx, c, in);
  }

  return inexactp ? sexp_make_flonum(ctx, negativep ? -val : val)
    : sexp_make_fixnum(negativep ? -val : val);
}

#if SEXP_USE_UTF8_STRINGS
static int sexp_decode_utf8_char(const unsigned char* s) {
  int i = s[0], len = strlen((const char*)s);
  if ((i >= 0xC0) && (i <= 0xF7) && (s[1]>>6 == 2)) {
    if ((i < 0xE0) && (len == 2)) {
      return ((i&0x3F)<<6) + (s[1]&0x3F);
    } else if ((i < 0xF0) && (len == 3) && (s[2]>>6 == 2)) {
      return ((i&0x1F)<<12) + ((s[1]&0x3F)<<6) + (s[2]&0x3F);
    } else if ((len == 4) && (s[2]>>6 == 2) && (s[3]>>6 == 2)) {
      return ((i&0x0F)<<16) + ((s[1]&0x3F)<<6) + ((s[2]&0x3F)<<6) + (s[3]&0x3F);
    }
  }
  return -1;
}
#endif

#if SEXP_USE_READER_LABELS
static sexp sexp_fill_reader_labels(sexp ctx, sexp x, sexp shares, int state) {
  sexp t, *p, *q;
  if (sexp_reader_labelp(x))
    return sexp_vector_data(shares)[sexp_unbox_reader_label(x)];
  if (!x || !sexp_pointerp(x) || sexp_markedp(x) == state)
    return x;
  sexp_markedp(x) = state;
  t = sexp_object_type(ctx, x);
  p = (sexp*) (((char*)x) + sexp_type_field_base(t));
  q = p + sexp_type_num_slots_of_object(t, x);
  for ( ; p < q; ++p)
    *p = sexp_fill_reader_labels(ctx, *p, shares, state);
  return x;
}
#endif

#if SEXP_USE_MINI_FLOAT_UNIFORM_VECTORS
/* Pre-computed 1.5.2 mini-float table (CUDA __NV_E5M2). */
/* We prefer a larger exponent for wider range. */
/* Note 9 is the first natural number that can't be represented exactly. */
/* Technically the implementation allows any hand-picked set of values. */
static const double sexp_quarters[] = {
              0.0,  1.52587890625e-05,  3.0517578125e-05,  4.57763671875e-05,
  6.103515625e-05,  7.62939453125e-05,  9.1552734375e-05, 0.0001068115234375,
  0.0001220703125,  0.000152587890625,  0.00018310546875,  0.000213623046875,
   0.000244140625,   0.00030517578125,   0.0003662109375,   0.00042724609375,
    0.00048828125,    0.0006103515625,    0.000732421875,    0.0008544921875,
     0.0009765625,     0.001220703125,     0.00146484375,     0.001708984375,
      0.001953125,      0.00244140625,      0.0029296875,      0.00341796875,
       0.00390625,       0.0048828125,       0.005859375,       0.0068359375,
        0.0078125,        0.009765625,        0.01171875,        0.013671875,
         0.015625,         0.01953125,         0.0234375,         0.02734375,
          0.03125,          0.0390625,          0.046875,          0.0546875,
           0.0625,           0.078125,           0.09375,           0.109375,
            0.125,            0.15625,            0.1875,            0.21875,
             0.25,             0.3125,             0.375,             0.4375,
              0.5,              0.625,              0.75,              0.875,
              1.0,               1.25,               1.5,               1.75,
              2.0,                2.5,               3.0,                3.5,
              4.0,                5.0,               6.0,                7.0,
              8.0,               10.0,              12.0,               14.0,
             16.0,               20.0,              24.0,               28.0,
             32.0,               40.0,              48.0,               56.0,
             64.0,               80.0,              96.0,              112.0,
            128.0,              160.0,             192.0,              224.0,
            256.0,              320.0,             384.0,              448.0,
            512.0,              640.0,             768.0,              896.0,
           1024.0,             1280.0,            1536.0,             1792.0,
           2048.0,             2560.0,            3072.0,             3584.0,
           4096.0,             5120.0,            6144.0,             7168.0,
           8192.0,            10240.0,           12288.0,            14336.0,
          16384.0,            20480.0,           24576.0,            28672.0,
          32768.0,            40960.0,           49152.0,            57344.0,
         INFINITY,                NAN,               NAN,                NAN
};

#define SEXP_QUARTERS_INFINITY_INDEX 124
#define SEXP_QUARTERS_NAN_INDEX 127

double sexp_quarter_to_double(unsigned char q) {
  return q < 128 ? sexp_quarters[q] : -sexp_quarters[q-128];
}

unsigned char sexp_double_to_quarter(double f) {
  int lo = 0, hi = SEXP_QUARTERS_INFINITY_INDEX - 1, mid;
  if (isnan(f)) return SEXP_QUARTERS_NAN_INDEX;
  if (f < 0) return 128 + sexp_double_to_quarter(-f);
  if (isinf(f)) return SEXP_QUARTERS_INFINITY_INDEX;
  while (lo <= hi) {
    mid = (lo + hi) / 2;
    if (sexp_quarters[mid] < f) {
      lo = mid + 1;
    } else if (sexp_quarters[mid] > f) {
      hi = mid - 1;
    } else {
      return mid;
    }
  }
  /* TODO: overflow to infinity? */
  return (sexp_quarters[lo] - f) < (f - sexp_quarters[hi]) ? lo : hi;
}

static unsigned int float_as_int(const float f) {
  union sexp_flonum_conv x;
  x.flonum = f;
  return x.bits;
}

static float int_as_float(const unsigned int n) {
  union sexp_flonum_conv x;
  x.bits = n;
  return x.flonum;
}

/* https://arxiv.org/abs/2112.08926 */
double sexp_half_to_double(unsigned short x) {
  unsigned int e, m, v;
  if (x == 31744) return INFINITY;
  if (x == 32767) return NAN;
  if (x == 64512) return -INFINITY;
  e = (x&0x7C00)>>10;
  m = (x&0x03FF)<<13;
  v = float_as_int((float)m)>>23;
  return int_as_float((x&0x8000)<<16 | (e!=0)*((e+112)<<23|m) | ((e==0)&(m!=0))*((v-37)<<23|((m<<(150-v))&0x007FE000)));
}

unsigned short sexp_double_to_half(double x) {
  unsigned int b, e, m;
  if (isnan(x)) return 32767;
  if (isinf(x)) return x < 0 ? 64512 : 31744;
  b = float_as_int(x)+0x00001000;
  e = (b&0x7F800000)>>23;
  m = b&0x007FFFFF;
  return (b&0x80000000)>>16 | (e>112)*((((e-112)<<10)&0x7C00)|m>>13) | ((e<113)&(e>101))*((((0x007FF000+m)>>(125-e))+1)>>1) | (e>143)*0x7FFF;
}
#endif

static int sexp_peek_char(sexp ctx, sexp in) {
  int c = sexp_read_char(ctx, in);
  if (c != EOF) sexp_push_char(ctx, c, in);
  return c;
}

#if SEXP_USE_UNIFORM_VECTOR_LITERALS
static int sexp_resolve_uniform_type(int c, sexp len) {
  switch (sexp_fixnump(len) ? sexp_unbox_fixnum(len) : 0) {
    case 1: if (c=='u') return SEXP_U1; break;
    case 8: if (c=='u') return SEXP_U8; if (c=='s') return SEXP_S8;
#if SEXP_USE_MINI_FLOAT_UNIFORM_VECTORS
      if (c=='f') return SEXP_F8;
#endif
      break;
    case 16: if (c=='u') return SEXP_U16; if (c=='s') return SEXP_S16;
#if SEXP_USE_MINI_FLOAT_UNIFORM_VECTORS
      if (c=='f') return SEXP_F16;
#endif
      break;
    case 32: if (c=='u') return SEXP_U32; if (c=='s') return SEXP_S32; if (c=='f') return SEXP_F32; break;
    case 64: if (c=='u') return SEXP_U64; if (c=='s') return SEXP_S64; if (c=='f') return SEXP_F64; if (c=='c') return SEXP_C64; break;
    case 128: if (c=='c') return SEXP_C128; break;
  }
  return SEXP_NOT_A_UNIFORM_TYPE;
}
#else
#define sexp_resolve_uniform_type(c, len) SEXP_U8
#endif

sexp sexp_list_to_uvector_op(sexp ctx, sexp self, sexp_sint_t n, sexp etype, sexp ls) {
  long et, i;
  long long min;
  unsigned long long max;
  sexp ls2, tmp;
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, etype);
  sexp_gc_var1(res);
  if (!sexp_listp(ctx, ls)) {
    res = sexp_exceptionp(ls) ? ls
      : sexp_xtype_exception(ctx, self, "list->uvector expected a list", ls);
  } else {
    sexp_gc_preserve1(ctx, res);
    et = sexp_unbox_fixnum(etype);
    res = et == SEXP_U8 ? sexp_make_bytes(ctx, sexp_length(ctx, ls), SEXP_VOID) : sexp_make_uvector(ctx, etype, sexp_length(ctx, ls));
    if (sexp_uvector_prefix(et) == 's') {
      min = (-1LL << (sexp_uvector_element_size(et)-1));
      max = (1LL << (sexp_uvector_element_size(et)-1)) - 1LL;
    } else {
      min = 0;
      max = sexp_uvector_element_size(et) == 64 ? -1 :
        (1uLL << sexp_uvector_element_size(et)) - 1LL;
    }
    for (ls2=ls; sexp_pairp(ls2); ls2=sexp_cdr(ls2)) {
      tmp = sexp_car(ls2);
      if (
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
          ((sexp_uvector_prefix(et) == 'u') || (sexp_uvector_prefix(et) == 's')) ?
#endif
          !((min == 0 && sexp_bignump(tmp) ? sexp_bignum_sign(tmp) > 0 : sexp_exact_integerp(tmp) && sexp_sint_value(tmp) >= min)
            && (sexp_sint_value(tmp) < 0 || sexp_uint_value(tmp) <= max))
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
          : ((sexp_uvector_prefix(et) == 'c') ? !sexp_numberp(tmp) :
          !(sexp_exact_integerp(tmp) || sexp_realp(tmp)))
#endif
          ) {
        res = sexp_cons(ctx, SEXP_FALSE, SEXP_FALSE);
        sexp_car(res) = sexp_make_integer(ctx, min);
        sexp_cdr(res) = sexp_make_integer(ctx, max);
        res = sexp_list2(ctx, res, tmp);
        res = sexp_xtype_exception(ctx, self, "invalid uniform vector value", res);
        break;
      }
    }
    if (!sexp_exceptionp(res)) {
      for (i=0; sexp_pairp(ls); ls=sexp_cdr(ls), i++) {
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
        switch (et) {
        case SEXP_U1:
          sexp_bit_set(res, i, sexp_unbox_fixnum(sexp_car(ls))); break;
        case SEXP_S8:
          ((signed char*)sexp_uvector_data(res))[i] = sexp_unbox_fixnum(sexp_car(ls)); break;
        case SEXP_U8:
#endif
          sexp_bytes_set(res, sexp_make_fixnum(i), sexp_car(ls));
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
          break;
        case SEXP_S16:
          ((signed short*)sexp_uvector_data(res))[i] = sexp_unbox_fixnum(sexp_car(ls)); break;
        case SEXP_U16:
          ((unsigned short*)sexp_uvector_data(res))[i] = sexp_unbox_fixnum(sexp_car(ls)); break;
        case SEXP_S32:
          ((int32_t*)sexp_uvector_data(res))[i] = sexp_sint_value(sexp_car(ls)); break;
        case SEXP_U32:
          ((uint32_t*)sexp_uvector_data(res))[i] = sexp_uint_value(sexp_car(ls)); break;
        case SEXP_S64:
          ((int64_t*)sexp_uvector_data(res))[i] = sexp_sint_value(sexp_car(ls)); break;
        case SEXP_U64:
          ((uint64_t*)sexp_uvector_data(res))[i] = sexp_uint_value(sexp_car(ls)); break;
#if SEXP_USE_FLONUMS
#if SEXP_USE_MINI_FLOAT_UNIFORM_VECTORS
        case SEXP_F8:
          ((unsigned char*)sexp_uvector_data(res))[i] = sexp_double_to_quarter(sexp_to_double(ctx, sexp_car(ls))); break;
        case SEXP_F16:
          ((unsigned short*)sexp_uvector_data(res))[i] = sexp_double_to_half(sexp_to_double(ctx, sexp_car(ls))); break;
#endif
        case SEXP_F32:
          ((float*)sexp_uvector_data(res))[i] = sexp_to_double(ctx, sexp_car(ls)); break;
        case SEXP_F64:
          ((double*)sexp_uvector_data(res))[i] = sexp_to_double(ctx, sexp_car(ls)); break;
#endif
#if SEXP_USE_COMPLEX
        case SEXP_C64:
          ((float*)sexp_uvector_data(res))[i*2] =
            sexp_to_double(ctx, sexp_real_part(sexp_car(ls)));
          ((float*)sexp_uvector_data(res))[i*2 + 1] =
            sexp_to_double(ctx, sexp_imag_part(sexp_car(ls)));
          break;
        case SEXP_C128:
          ((double*)sexp_uvector_data(res))[i*2] =
            sexp_to_double(ctx, sexp_real_part(sexp_car(ls)));
          ((double*)sexp_uvector_data(res))[i*2 + 1] =
            sexp_to_double(ctx, sexp_imag_part(sexp_car(ls)));
          break;
#endif
        }
#endif  /* SEXP_USE_UNIFORM_VECTOR_LITERALS */
      }
    }
    sexp_gc_release1(ctx);
  }
  return res;
}

sexp sexp_read_one (sexp ctx, sexp in, sexp *shares);

sexp sexp_read_raw (sexp ctx, sexp in, sexp *shares) {
  char *str;
  int c1, c2, line;
  sexp tmp2;
  sexp_gc_var2(res, tmp);
  sexp_gc_preserve2(ctx, res, tmp);

 scan_loop:
  switch (c1 = sexp_read_char(ctx, in)) {
  case EOF:
    res = SEXP_EOF;
    break;
  case ';':
    while ((c1 = sexp_read_char(ctx, in)) != EOF)
      if (c1 == '\n')
        break;
    /* ... FALLTHROUGH ... */
  case '\n':
    sexp_port_line(in)++;
    goto scan_loop;
  case ' ':
  case '\t':
  case '\f':
  case '\r':
    goto scan_loop;
  case '\'':
    res = sexp_read_one(ctx, in, shares);
    if (! sexp_exceptionp(res))
      res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_QUOTE_SYMBOL), res);
    break;
  case '`':
    res = sexp_read_one(ctx, in, shares);
    if (! sexp_exceptionp(res))
      res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_QUASIQUOTE_SYMBOL), res);
    break;
  case ',':
    if ((c1 = sexp_read_char(ctx, in)) == '@') {
      res = sexp_read_one(ctx, in, shares);
      if (! sexp_exceptionp(res))
        res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_UNQUOTE_SPLICING_SYMBOL), res);
    } else {
      sexp_push_char(ctx, c1, in);
      res = sexp_read_one(ctx, in, shares);
      if (! sexp_exceptionp(res))
        res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_UNQUOTE_SYMBOL), res);
    }
    break;
  case '"':
    res = sexp_read_string(ctx, in, '"');
    break;
  case '(':
    line = (sexp_port_sourcep(in) ? sexp_port_line(in) : -1);
    res = SEXP_NULL;
    tmp = sexp_read_raw(ctx, in, shares);
    while ((tmp != SEXP_EOF) && (tmp != SEXP_CLOSE) && (tmp != SEXP_RAWDOT)) {
      if (sexp_exceptionp(tmp)) {
        res = tmp;
        break;
      }
      res = sexp_cons(ctx, tmp, res);
      if (sexp_port_sourcep(in) && (line >= 0))
        sexp_pair_source(res)
          = sexp_cons(ctx, sexp_port_name(in), sexp_make_fixnum(line));
      tmp = sexp_read_raw(ctx, in, shares);
    }
    if (! sexp_exceptionp(res)) {
      if (tmp == SEXP_RAWDOT) { /* dotted list */
        if (res == SEXP_NULL) {
          res = sexp_read_error(ctx, "dot before any elements in list",
                                SEXP_NULL, in);
        } else {
          tmp = sexp_read_raw(ctx, in, shares);
          if (tmp == SEXP_EOF) {
            res = sexp_read_incomplete_error(ctx, "no input after dot",
                                             SEXP_NULL, in);
          } else if (sexp_exceptionp(tmp)) {
            res = tmp;
          } else if (tmp == SEXP_CLOSE) {
            res = sexp_read_error(ctx, "no final element in list after dot",
                                  SEXP_NULL, in);
          } else if (sexp_read_raw(ctx, in, shares) != SEXP_CLOSE) {
            res = sexp_read_error(ctx, "multiple tokens in dotted tail",
                                  SEXP_NULL, in);
          } else if (tmp == SEXP_RAWDOT) {
            res = sexp_read_error(ctx, "multiple dots in list",
                                  SEXP_NULL, in);
          } else {
            tmp2 = res;
            res = sexp_nreverse(ctx, res);
            sexp_cdr(tmp2) = tmp;
          }
        }
      } else if (tmp == SEXP_CLOSE) {
        res = (sexp_pairp(res) ? sexp_nreverse(ctx, res) : res);
      } else {
        res = sexp_read_incomplete_error(ctx, "missing trailing ')' started on line",
                                         sexp_make_fixnum(line), in);
      }
    }
    if ((line >= 0) && sexp_pairp(res)) {
      sexp_pair_source(res)
        = sexp_cons(ctx, sexp_port_name(in), sexp_make_fixnum(line));
      for (tmp=sexp_cdr(res); sexp_pairp(tmp); tmp=sexp_cdr(tmp))
        sexp_pair_source(tmp) = sexp_pair_source(res);
    }
    if (sexp_port_sourcep(in))
      for (tmp=res; sexp_pairp(tmp); tmp=sexp_cdr(tmp))
        sexp_immutablep(tmp) = 1;
    break;
#if SEXP_USE_OBJECT_BRACE_LITERALS
  case '{':
    res = sexp_read_symbol(ctx, in, EOF, 0);
    if (!sexp_exceptionp(res)) {
      for (c1=' '; isspace(c1); c1=sexp_read_char(ctx, in))
        ;
      if (c1=='#') {
        tmp = sexp_read_one(ctx, in, shares);
        if (sexp_symbolp(tmp) && tmp == sexp_intern(ctx, "t", 1))
          tmp = SEXP_TRUE;
        else if (!sexp_fixnump(tmp))
          tmp = sexp_read_error(ctx, "invalid type identifier", tmp, in);
      } else if (c1=='"') {
        tmp = sexp_read_string(ctx, in, '"');
      } else {
        tmp = sexp_read_error(ctx, "brace literal missing type identifier", sexp_make_character(c1), in);
      }
      if (!sexp_exceptionp(tmp)) tmp = sexp_lookup_type(ctx, res, tmp);
      if (tmp && sexp_typep(tmp) && sexp_type_tag(tmp) == SEXP_STRING_CURSOR) {
        res = sexp_make_string_cursor(sexp_unbox_fixnum(sexp_read_raw(ctx, in, shares)));
        tmp2 = sexp_read_raw(ctx, in, shares);
        if (tmp2 != SEXP_CLOSE_BRACE)
          res = sexp_read_error(ctx, "expected closing brace in string-cursor, got", tmp2, in);
      } else if (tmp && sexp_typep(tmp) && sexp_type_print(tmp)
          && sexp_opcodep(sexp_type_print(tmp))
          && sexp_opcode_func(sexp_type_print(tmp)) == (sexp_proc1)sexp_write_simple_object) {
        res = sexp_alloc_tagged(ctx, sexp_type_size_base(tmp), sexp_type_tag(tmp));
        for (c1=0; ; c1++) {
          tmp2 = sexp_read_raw(ctx, in, shares);
          if (sexp_exceptionp(tmp2)) {
            res = tmp2;
            break;
          } else if (tmp2 == SEXP_CLOSE_BRACE) {
            break;
          } else if (c1 >= sexp_type_field_len_base(tmp)) {
            res = sexp_read_error(ctx, "too many slots in object literal", res, in);
            break;
          } else {
            sexp_slot_set(res, c1, tmp2);
          }
        }
      } else {
        res = sexp_exceptionp(tmp) ? tmp : sexp_read_error(ctx, "invalid type for brace literals", tmp, in);
      }
    }
    break;
#endif
  case '#':
    switch (c1=sexp_read_char(ctx, in)) {
    case 'b': case 'B':
      res = sexp_read_number(ctx, in, 2, 0); break;
    case 'o': case 'O':
      res = sexp_read_number(ctx, in, 8, 0); break;
    case 'd': case 'D':
      res = sexp_read_number(ctx, in, 10, 0); break;
    case 'x': case 'X':
      res = sexp_read_number(ctx, in, 16, 0); break;
    case 'e': case 'E':
      res = sexp_read_number(ctx, in, 10, 1);
#if SEXP_USE_INFINITIES
      if (sexp_flonump(res)
          && (isnan(sexp_flonum_value(res)) || isinf(sexp_flonum_value(res))))
        res = sexp_read_error(ctx, "can't convert non-finite flonum to exact", res, in);
      else
#endif
#if SEXP_USE_COMPLEX
      if (sexp_complexp(res))
        res = sexp_inexact_to_exact(ctx, NULL, 1, res);
      else
#endif
      if (sexp_flonump(res))
#if SEXP_USE_RATIOS
        res = sexp_double_to_ratio(ctx, sexp_flonum_value(res));
#elif SEXP_USE_BIGNUMS
        res = sexp_bignum_normalize(sexp_double_to_bignum(ctx, sexp_flonum_value(res)));
#else
        res = sexp_make_fixnum(sexp_flonum_value(res));
#endif
      break;
    case 'i': case 'I':
      res = sexp_read(ctx, in);
      if (sexp_exact_integerp(res))
        res = sexp_make_flonum(ctx, sexp_unbox_fixnum(res));
#if SEXP_USE_RATIOS
      else if (sexp_ratiop(res))
        res = sexp_make_flonum(ctx, sexp_ratio_to_double(ctx, res));
#endif
      break;
    case 'f': case 'F':
    case 't': case 'T':
      c2 = sexp_read_char(ctx, in);
      if (c2 == EOF || sexp_is_separator(c2)) {
        res = (sexp_tolower(c1) == 't' ? SEXP_TRUE : SEXP_FALSE);
        sexp_push_char(ctx, c2, in);
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
      } else if (sexp_isdigit(c2)) {
        sexp_push_char(ctx, c2, in);
        goto read_uvector;
#endif
      } else {
        sexp_push_char(ctx, c2, in);
        res = sexp_read_symbol(ctx, in, c1, 0);
        if (!sexp_exceptionp(res)) {
          if (strcasecmp("true", sexp_string_data(res)) == 0)
            res = SEXP_TRUE;
          else if (strcasecmp("false", sexp_string_data(res)) == 0)
            res = SEXP_FALSE;
          else
            res = sexp_read_error(ctx, "invalid # syntax", res, in);
        }
      }
      break;
#if SEXP_USE_BYTEVECTOR_LITERALS
    case 'v': case 'V':
      c1 = sexp_read_char(ctx, in);
      if (!(c1=='u'||c1=='U')) {
        res = sexp_read_error(ctx, "invalid syntax #v%c", sexp_make_character(c1), in);
        break;
      }
      /* ... FALLTHROUGH ... */
    case 'u': case 'U':
#if SEXP_USE_UNIFORM_VECTOR_LITERALS
    case 's': case 'S':
    case 'c': case 'C':
    read_uvector:
#endif
      res = sexp_read_number(ctx, in, 10, 1);
      c2 = sexp_resolve_uniform_type(sexp_tolower(c1), res);
      if (sexp_exceptionp(res)) {
      } else if (c2 != SEXP_NOT_A_UNIFORM_TYPE) {
        tmp = sexp_read_one(ctx, in, shares);
        res = sexp_list_to_uvector(ctx, sexp_make_fixnum(c2), tmp);
        if (!sexp_exceptionp(res)) sexp_immutablep(res) = 1;
      } else {
        tmp = sexp_list2(ctx, sexp_make_character(c1), res);
        res = sexp_read_error(ctx, "invalid uniform vector syntax #%c%c", tmp, in);
      }
      break;
#endif
#if SEXP_USE_READER_LABELS
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      c2 = digit_value(c1);
      while (isdigit(c1=sexp_read_char(ctx, in)))
        c2 = c2 * 10 + digit_value(c1);
      tmp = sexp_make_fixnum(c2);
      if (c1 == '#') {
        if (!sexp_vectorp(*shares) ||
            tmp > sexp_vector_data(*shares)[sexp_vector_length(*shares)-1] ||
            sexp_vector_data(*shares)[c2] == SEXP_VOID) {
          res = sexp_read_error(ctx, "unknown reader label", tmp, in);
        }
        else
          res = sexp_vector_data(*shares)[c2];
      } else if (c1 == '=') {
        if (!sexp_vectorp(*shares)) {
          *shares = sexp_make_vector(ctx, sexp_make_fixnum(24), SEXP_VOID);
          sexp_vector_data(*shares)[23] = SEXP_ZERO;
        }
        if (tmp >
            sexp_fx_add(sexp_vector_data(*shares)[sexp_vector_length(*shares)-1],
                        sexp_make_fixnum(16))) {
          res = sexp_read_error(ctx, "reader label out of order", tmp, in);
        } else {
          if (c2 + 1 >= (int)sexp_vector_length(*shares)) {
            tmp2 = sexp_make_vector(ctx, sexp_make_fixnum(sexp_vector_length(*shares)*2), SEXP_VOID);
            memcpy(sexp_vector_data(tmp2), sexp_vector_data(*shares), (sexp_vector_length(*shares)-1)*sizeof(sexp));
            *shares = tmp2;
          }
          sexp_vector_data(*shares)[c2] = sexp_make_reader_label(c2);
          if (tmp > sexp_vector_data(*shares)[sexp_vector_length(*shares)-1])
            sexp_vector_data(*shares)[sexp_vector_length(*shares)-1] = tmp;
          res = sexp_read_one(ctx, in, shares);
          sexp_vector_data(*shares)[c2] = res;
          if (sexp_reader_labelp(res))
            res = sexp_read_error(ctx, "self reader label reference", tmp, in);
          else
            sexp_vector_data(*shares)[c2] = res;
        }
      } else {
        res = sexp_read_error(ctx, "expected # or = after #<n>", sexp_make_character(c1), in);
      }
      break;
#endif
    case ';':
      tmp = sexp_read_one(ctx, in, shares);   /* discard */
      if (sexp_exceptionp(tmp))
        res = tmp;
      else
        goto scan_loop;
      break;
    case '|':
      for (c2 = 1; c2 > 0 && c1 != EOF; ) {
        c1 = sexp_read_char(ctx, in);
        if (c1 == '#') {
          while ((c1 = sexp_read_char(ctx, in)) == '#')
            ;
          if (c1 == '|') c2++;
        } else if (c1 == '|') {
          while ((c1 = sexp_read_char(ctx, in)) == '|')
            ;
          if (c1 == '#') c2--;
        } else if (c1 == '\n') {
          sexp_port_line(in)++;
        }
      }
      if (c1 == EOF)
        res = sexp_read_incomplete_error(ctx, "unterminated #| comment", SEXP_NULL, in);
      else
        goto scan_loop;
      break;
    case '!':
      c1 = sexp_read_char(ctx, in);
      if (isspace(c1) || c1 == '/') {
        while ((c1 = sexp_read_char(ctx, in)) != EOF)
          if (c1 == '\n')
            break;
        sexp_port_line(in)++;
        res = SEXP_VOID;
      } else {
        sexp_push_char(ctx, c1, in);
        res = sexp_read_symbol(ctx, in, '!', 0);
        if (SEXP_USE_FOLD_CASE_SYMS && sexp_stringp(res)
            && strcasecmp("!fold-case", sexp_string_data(res)) == 0) {
          sexp_port_fold_casep(in) = 1;
        } else if (SEXP_USE_FOLD_CASE_SYMS && sexp_stringp(res)
                   && strcasecmp("!no-fold-case", sexp_string_data(res)) == 0) {
          sexp_port_fold_casep(in) = 0;
        } else {
          res = sexp_read_error(ctx, "unknown #! symbol", res, in);
        }
      }
      if (!sexp_exceptionp(res))
        goto scan_loop;
      break;
    case '\\':
      c1 = sexp_read_char(ctx, in);
      c2 = sexp_read_char(ctx, in);
      sexp_push_char(ctx, c2, in);
      if ((c1 == 'x' || c1 == 'X') && (sexp_isxdigit(c2))) {
        res = sexp_read_number(ctx, in, 16, 0);
        if (sexp_fixnump(res) && sexp_unbox_fixnum(res) >= 0 && sexp_unbox_fixnum(res) <= 0x10FFFF)
          res = sexp_make_character(sexp_unbox_fixnum(res));
        else if (!sexp_exceptionp(res))
          res = sexp_read_error(ctx, "bad character #\\x literal", res, in);
      } else {
        res = sexp_read_symbol(ctx, in, c1, 0);
        if (sexp_stringp(res)) {
          str = sexp_string_data(res);
          if (sexp_string_size(res) == 0)
            res =
              sexp_read_error(ctx, "unexpected end of character literal",
                              SEXP_NULL, in);
          if (sexp_string_size(res) == 1) {
            res = sexp_make_character(c1);
          } else {
            res = 0;
            for (c2=0; c2 < sexp_num_char_names; c2++) {
              if ((sexp_port_fold_casep(in) ? strcasecmp : strcmp)(str, sexp_char_names[c2].name) == 0) {
                res = sexp_make_character(sexp_char_names[c2].ch);
                break;
              }
            }
            if (!res) {
#if SEXP_USE_UTF8_STRINGS
              if ((c1=sexp_decode_utf8_char((unsigned char*)str)) > 0) {
                res = sexp_make_character(c1);
                break;
              }
#endif
              tmp = sexp_c_string(ctx, str, -1);
              res = sexp_read_error(ctx, "unknown character name", tmp, in);
            }
          }
        }
      }
      break;
    case '(':
      sexp_push_char(ctx, c1, in);
      res = sexp_read_one(ctx, in, shares);
      if (sexp_not(sexp_listp(ctx, res))) {
        if (! sexp_exceptionp(res)) {
          res = sexp_read_error(ctx, "dotted list not allowed in vector syntax",
                                SEXP_NULL,
                                in);
        }
      } else {
        res = sexp_list_to_vector(ctx, res);
      }
      break;
    case '\'':
      res = sexp_read_one(ctx, in, shares);
      if (! sexp_exceptionp(res))
	res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_SYNTAX_SYMBOL), res);
      break;
    case '`':
      res = sexp_read_one(ctx, in, shares);
      if (! sexp_exceptionp(res))
	res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_QUASISYNTAX_SYMBOL), res);
      break;
    case ',':
      if ((c1 = sexp_read_char(ctx, in)) == '@') {
	res = sexp_read_one(ctx, in, shares);
	if (! sexp_exceptionp(res))
	  res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_UNSYNTAX_SPLICING_SYMBOL), res);
      } else {
	sexp_push_char(ctx, c1, in);
	res = sexp_read_one(ctx, in, shares);
	if (! sexp_exceptionp(res))
	  res = sexp_list2(ctx, sexp_global(ctx, SEXP_G_UNSYNTAX_SYMBOL), res);
      }
      break;
    default:
      res = sexp_read_error(ctx, "invalid char following '#'",
                            c1 == EOF ? SEXP_EOF : sexp_make_character(c1), in);
    }
    break;
  case '.':
    c1 = sexp_read_char(ctx, in);
    sexp_push_char(ctx, c1, in);
    if (c1 == EOF || sexp_is_separator(c1)) {
      res = SEXP_RAWDOT;
    } else if (sexp_isdigit(c1)) {
      res = sexp_read_float_tail(ctx, in, 0, 0);
    } else {
      res = sexp_read_symbol(ctx, in, '.', 1);
    }
    break;
  case ')':
    res = SEXP_CLOSE;
    break;
#if SEXP_USE_OBJECT_BRACE_LITERALS
  case '}':
    res = SEXP_CLOSE_BRACE;
    break;
#endif
  case '|':
    res = sexp_read_string(ctx, in, '|');
    if (sexp_stringp(res))
      res = sexp_intern(ctx, sexp_string_data(res), sexp_string_size(res));
    break;
  case '+':
  case '-':
    c2 = sexp_read_char(ctx, in);
    if ((c2 == '.' && sexp_isdigit(sexp_peek_char(ctx, in)))
        || sexp_isdigit(c2)) {
      sexp_push_char(ctx, c2, in);
      res = sexp_read_number(ctx, in, 10, 0);
      if ((c1 == '-') && ! sexp_exceptionp(res)) {
#if SEXP_USE_FLONUMS
        if (sexp_flonump(res))
#if SEXP_USE_IMMEDIATE_FLONUMS
          res = sexp_make_flonum(ctx, -1 * sexp_flonum_value(res));
#else
          sexp_flonum_value(res) = -1 * sexp_flonum_value(res);
#endif
        else
#endif
#if SEXP_USE_BIGNUMS
          if (sexp_bignump(res)) {
            if ((sexp_bignum_hi(res) == 1)
                && (sexp_bignum_data(res)[0] == (SEXP_MAX_FIXNUM+1)))
              res = sexp_make_fixnum(-sexp_bignum_data(res)[0]);
            else
              sexp_bignum_sign(res) = -sexp_bignum_sign(res);
          } else
#endif
#if SEXP_USE_RATIOS
          if (sexp_ratiop(res)) {
            sexp_negate(sexp_ratio_numerator(res));
          } else
#endif
#if SEXP_USE_COMPLEX
          if (sexp_complexp(res)) {
            if (sexp_complex_real(res) == SEXP_ZERO) {
              sexp_negate_maybe_ratio(sexp_complex_imag(res));
            } else {
              sexp_negate_maybe_ratio(sexp_complex_real(res));
            }
          } else
#endif
            res = sexp_fx_mul(res, SEXP_NEG_ONE);
      }
    } else {
      sexp_push_char(ctx, c2, in);
      res = sexp_read_symbol(ctx, in, c1, !SEXP_USE_INFINITIES);
#if SEXP_USE_INFINITIES
      if (sexp_stringp(res)) {
        str = sexp_string_data(res);
        if (strcasecmp(str, "+inf.0") == 0)
          res = sexp_make_flonum(ctx, sexp_pos_infinity);
        else if (strcasecmp(str, "-inf.0") == 0)
          res = sexp_make_flonum(ctx, sexp_neg_infinity);
        else if (strcasecmp(str+1, "nan.0") == 0)
          res = sexp_make_flonum(ctx, sexp_nan);
#if SEXP_USE_COMPLEX
        else if (strncasecmp(str+1, "inf.0", 5) == 0) {
          tmp = sexp_make_flonum(ctx, c1 == '+' ? sexp_pos_infinity : sexp_neg_infinity);
          if (str[6] == 0) {
            res = tmp;
          } else if ((str[6] == 'i' || str[6] == 'I') && str[7] == 0) {
            res = sexp_make_complex(ctx, SEXP_ZERO, tmp);
#if SEXP_USE_MATH
          } else if (str[6] == '@') {
            res = sexp_substring_cursor(ctx, res, sexp_make_string_cursor(6), SEXP_FALSE);
            res = sexp_open_input_string(ctx, res);
            res = sexp_read_polar_tail(ctx, res, tmp);
#endif
          } else if (str[6] == '+' || str[6] == '-') {
            res = sexp_substring_cursor(ctx, res, sexp_make_string_cursor(6), SEXP_FALSE);
            res = sexp_string_to_number(ctx, res, SEXP_TEN);
            if (sexp_complexp(res) && (sexp_complex_real(res) == SEXP_ZERO))
              sexp_complex_real(res) = tmp;
            else if (res == SEXP_ZERO)
              res = tmp;
            else if (!sexp_exceptionp(res))
              res = sexp_read_error(ctx, "invalid complex infinity", res, in);
          } else {
            res = sexp_read_error(ctx, "invalid infinity", res, in);
          }
        }
#endif
        else
          res = sexp_intern(ctx, str, sexp_string_size(res));
      }
#endif
#if SEXP_USE_COMPLEX
      if (res == sexp_intern(ctx, "+i", -1))
        res = sexp_make_complex(ctx, SEXP_ZERO, SEXP_ONE);
      else if (res == sexp_intern(ctx, "-i", -1))
        res = sexp_make_complex(ctx, SEXP_ZERO, SEXP_NEG_ONE);
#endif
    }
    break;
  case '0': case '1': case '2': case '3': case '4':
  case '5': case '6': case '7': case '8': case '9':
    sexp_push_char(ctx, c1, in);
    res = sexp_read_number(ctx, in, 10, 0);
    break;
  default:
    res = sexp_read_symbol(ctx, in, c1, 1);
    break;
  }

  if (sexp_port_sourcep(in) && sexp_pointerp(res))
    sexp_immutablep(res) = 1;
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_read_one (sexp ctx, sexp in, sexp *shares) {
  sexp res = sexp_read_raw(ctx, in, shares);
  if (res == SEXP_CLOSE)
    res = sexp_read_error(ctx, "too many ')'s", SEXP_NULL, in);
#if SEXP_USE_OBJECT_BRACE_LITERALS
  else if (res == SEXP_CLOSE_BRACE)
    res = sexp_read_error(ctx, "too many '}'s", SEXP_NULL, in);
#endif
  else if (res == SEXP_RAWDOT)
    res = sexp_read_error(ctx, "unexpected '.'", SEXP_NULL, in);
  return res;
}

sexp sexp_read_op (sexp ctx, sexp self, sexp_sint_t n, sexp in) {
  sexp res;
  sexp_gc_var1(shares);
  sexp_assert_type(ctx, sexp_iportp, SEXP_IPORT, in);
  sexp_check_block_port(ctx, in, 0);
  sexp_gc_preserve1(ctx, shares);
  res = sexp_read_one(ctx, in, &shares);
#if SEXP_USE_READER_LABELS
  if (!sexp_exceptionp(res) && sexp_vectorp(shares)) {
    res = sexp_fill_reader_labels(ctx, res, shares, 1);  /* mark=1 */
    res = sexp_fill_reader_labels(ctx, res, shares, 0);  /* mark=0 */
  }
#endif
  sexp_maybe_unblock_port(ctx, in);
  sexp_gc_release1(ctx);
  return res;
}

sexp sexp_read_from_string (sexp ctx, const char *str, sexp_sint_t len) {
  sexp res;
  sexp_gc_var2(s, in);
  sexp_gc_preserve2(ctx, s, in);
  s = sexp_c_string(ctx, str, len);
  in = sexp_open_input_string(ctx, s);
  res = sexp_read(ctx, in);
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_string_to_number_op (sexp ctx, sexp self, sexp_sint_t n, sexp str, sexp b) {
  int base;
  sexp_gc_var2(in, res);
  sexp_assert_type(ctx, sexp_stringp, SEXP_STRING, str);
  sexp_assert_type(ctx, sexp_fixnump, SEXP_FIXNUM, b);
  if (((base=sexp_unbox_fixnum(b)) < 2) || (base > 36))
    return sexp_user_exception(ctx, self, "invalid numeric base", b);
  if (sexp_string_data(str)[0]=='\0'
      || (sexp_string_data(str)[1]=='\0' && !sexp_isxdigit((unsigned char)(sexp_string_data(str)[0]))))
    return SEXP_FALSE;
  sexp_gc_preserve2(ctx, in, res);
  in = sexp_open_input_string(ctx, str);
  if (sexp_string_data(str)[0] == '+') {
    if (sexp_isdigit((unsigned char)(sexp_string_data(str)[1]))
        || sexp_string_data(str)[1] == '.' || sexp_string_data(str)[1] == '#')
      sexp_read_char(ctx, in);
  }
  res = ((sexp_string_data(str)[0] == '#' &&
          sexp_tolower((unsigned char)sexp_string_data(str)[1]) != 'e' &&
          sexp_tolower((unsigned char)sexp_string_data(str)[1]) != 'i')
         || base == 10 ? sexp_read(ctx, in) :
         sexp_read_number(ctx, in, base, 0));
  if (!sexp_numberp(res) || sexp_peek_char(ctx, in) != EOF)
    res = SEXP_FALSE;
  sexp_gc_release2(ctx);
  return res;
}

sexp sexp_write_to_string (sexp ctx, sexp obj) {
  sexp str;
  sexp_gc_var1(out);
  sexp_gc_preserve1(ctx, out);
  out = sexp_open_output_string(ctx);
  str = sexp_write(ctx, obj, out);
  if (! sexp_exceptionp(str))
    str = sexp_get_output_string(ctx, out);
  sexp_gc_release1(ctx);
  return str;
}

sexp sexp_symbol_to_string_op (sexp ctx, sexp self, sexp_sint_t n, sexp sym) {
#if SEXP_USE_HUFF_SYMS
  if (sexp_isymbolp(sym)) return sexp_write_to_string(ctx, sym);
#endif
  sexp_assert_type(ctx, sexp_lsymbolp, SEXP_SYMBOL, sym);
  return sexp_c_string(ctx, sexp_lsymbol_data(sym), sexp_lsymbol_length(sym));
}

void sexp_init (void) {
#if SEXP_USE_GLOBAL_SYMBOLS
  int i;
#endif
  if (! sexp_initialized_p) {
    sexp_initialized_p = 1;
#if SEXP_USE_BOEHM
    GC_init();
#if SEXP_USE_GLOBAL_SYMBOLS
    GC_add_roots((char*)&sexp_symbol_table,
                 ((char*)&sexp_symbol_table)+sizeof(sexp_symbol_table)+1);
#endif
#elif ! SEXP_USE_MALLOC
    sexp_gc_init();
#endif
#if SEXP_USE_GLOBAL_SYMBOLS
    for (i=0; i<SEXP_SYMBOL_TABLE_SIZE; i++)
      sexp_symbol_table[i] = SEXP_NULL;
#endif
  }
}
