view libinterp/corefcn/oct-stream.cc @ 21502:7a19c5678f91

move textscan class to oct-stream.cc and textscan function to file-io.cc * file-io.cc (Ftextscan): Move here. * textxcan.cc: From here. * oct-stream.h, oct-stream.cc (delimited_stream, textscan_format_elt, textscan_format_list, textscan): Move here. * textscan.h, textscan.cc: From here. * textscan.h, textscan.cc: Delete. * libinterp/corefcn/module.mk: Update.
author John W. Eaton <jwe@octave.org>
date Sat, 19 Mar 2016 11:21:45 -0400
parents 4fc04d04dd9c
children 20bf0ec536e2
line wrap: on
line source

/*

Copyright (C) 1996-2015 John W. Eaton
Copyright (C) 2015-2016 Lachlan Andrew, Monash University

This file is part of Octave.

Octave is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.

Octave is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with Octave; see the file COPYING.  If not, see
<http://www.gnu.org/licenses/>.

*/

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <cassert>
#include <cctype>
#include <cstring>

#include <deque>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

#include "Array.h"
#include "Cell.h"
#include "byte-swap.h"
#include "lo-ieee.h"
#include "lo-mappers.h"
#include "lo-utils.h"
#include "oct-locbuf.h"
#include "quit.h"
#include "singleton-cleanup.h"
#include "str-vec.h"

#include "error.h"
#include "errwarn.h"
#include "input.h"
#include "oct-stdstrm.h"
#include "oct-stream.h"
#include "ov.h"
#include "ovl.h"
#include "toplev.h"
#include "utils.h"

// Programming Note: There are two very different error functions used
// in the stream code.  When invoked with "error (...)" the member
// function from octave_stream or octave_base_stream is called.  This
// function sets the error state on the stream AND returns control to
// the caller.  The caller must then return a value at the end of the
// function.  When invoked with "::error (...)" the exception-based
// error function from error.h is used.  This function will throw an
// exception and not return control to the caller.  BE CAREFUL and
// invoke the correct error function!

// Possible values for conv_err:
//
//   1 : not a real scalar
//   2 : value is NaN
//   3 : value is not an integer

static int
convert_to_valid_int (const octave_value& tc, int& conv_err)
{
  conv_err = 0;

  int retval = 0;

  double dval = 0.0;

  try
    {
      dval = tc.double_value ();
    }
  catch (const octave_execution_exception&)
    {
      recover_from_exception ();

      conv_err = 1;
    }

  if (! conv_err)
    {
      if (! lo_ieee_isnan (dval))
        {
          int ival = NINT (dval);

          if (ival == dval)
            retval = ival;
          else
            conv_err = 3;
        }
      else
        conv_err = 2;
    }

  return retval;
}

static int
get_size (double d, const std::string& who)
{
  int retval = -1;

  if (lo_ieee_isnan (d))
    ::error ("%s: NaN is invalid as size specification", who.c_str ());

  if (xisinf (d))
    retval = -1;
  else
    {
      if (d < 0.0)
        ::error ("%s: negative value invalid as size specification",
                 who.c_str ());

      retval = NINT (d);
    }

  return retval;
}

static void
get_size (const Array<double>& size, octave_idx_type& nr, octave_idx_type& nc,
          bool& one_elt_size_spec, const std::string& who)
{
  nr = -1;
  nc = -1;

  one_elt_size_spec = false;

  double dnr = -1.0;
  double dnc = -1.0;

  octave_idx_type sz_len = size.numel ();

  if (sz_len == 1)
    {
      one_elt_size_spec = true;

      dnr = size(0);

      dnc = (dnr == 0.0) ? 0.0 : 1.0;
    }
  else if (sz_len == 2)
    {
      dnr = size(0);

      if (xisinf (dnr))
        ::error ("%s: invalid size specification", who.c_str ());

      dnc = size(1);
    }
  else
    ::error ("%s: invalid size specification", who.c_str ());

  nr = get_size (dnr, who);

  if (dnc >= 0.0)
    nc = get_size (dnc, who);
}

class
scanf_format_elt
{
public:

  enum special_conversion
  {
    whitespace_conversion = 1,
    literal_conversion = 2,
    null = 3
  };

  scanf_format_elt (const char *txt = 0, int w = 0, bool d = false,
                    char typ = '\0', char mod = '\0',
                    const std::string& ch_class = "")
    : text (strsave (txt)), width (w), discard (d), type (typ),
      modifier (mod), char_class (ch_class)
  { }

  scanf_format_elt (const scanf_format_elt& e)
    : text (strsave (e.text)), width (e.width), discard (e.discard),
      type (e.type), modifier (e.modifier), char_class (e.char_class)
  { }

  scanf_format_elt& operator = (const scanf_format_elt& e)
  {
    if (this != &e)
      {
        text = strsave (e.text);
        width = e.width;
        discard = e.discard;
        type = e.type;
        modifier = e.modifier;
        char_class = e.char_class;
      }

    return *this;
  }

  ~scanf_format_elt (void) { delete [] text; }

  // The C-style format string.
  const char *text;

  // The maximum field width.
  int width;

  // TRUE if we are not storing the result of this conversion.
  bool discard;

  // Type of conversion -- 'd', 'i', 'o', 'u', 'x', 'e', 'f', 'g',
  // 'c', 's', 'p', '%', or '['.
  char type;

  // A length modifier -- 'h', 'l', or 'L'.
  char modifier;

  // The class of characters in a '[' format.
  std::string char_class;
};

class
scanf_format_list
{
public:

  scanf_format_list (const std::string& fmt = "");

  ~scanf_format_list (void);

  octave_idx_type num_conversions (void) { return nconv; }

  // The length can be different than the number of conversions.
  // For example, "x %d y %d z" has 2 conversions but the length of
  // the list is 3 because of the characters that appear after the
  // last conversion.

  size_t length (void) const { return fmt_elts.size (); }

  const scanf_format_elt *first (void)
  {
    curr_idx = 0;
    return current ();
  }

  const scanf_format_elt *current (void) const
  {
    return length () > 0 ? fmt_elts[curr_idx] : 0;
  }

  const scanf_format_elt *next (bool cycle = true)
  {
    static scanf_format_elt dummy
      (0, 0, false, scanf_format_elt::null, '\0', "");

    curr_idx++;

    if (curr_idx >= length ())
      {
        if (cycle)
          curr_idx = 0;
        else
          return &dummy;
      }

    return current ();
  }

  void printme (void) const;

  bool ok (void) const { return (nconv >= 0); }

  operator bool () const { return ok (); }

  bool all_character_conversions (void);

  bool all_numeric_conversions (void);

private:

  // Number of conversions specified by this format string, or -1 if
  // invalid conversions have been found.
  octave_idx_type nconv;

  // Index to current element;
  size_t curr_idx;

  // List of format elements.
  std::deque<scanf_format_elt*> fmt_elts;

  // Temporary buffer.
  std::ostringstream *buf;

  void add_elt_to_list (int width, bool discard, char type, char modifier,
                        const std::string& char_class = "");

  void process_conversion (const std::string& s, size_t& i, size_t n,
                           int& width, bool& discard, char& type,
                           char& modifier);

  int finish_conversion (const std::string& s, size_t& i, size_t n,
                         int& width, bool discard, char& type,
                         char modifier);
  // No copying!

  scanf_format_list (const scanf_format_list&);

  scanf_format_list& operator = (const scanf_format_list&);
};

scanf_format_list::scanf_format_list (const std::string& s)
  : nconv (0), curr_idx (0), fmt_elts (), buf (0)
{
  size_t n = s.length ();

  size_t i = 0;

  int width = 0;
  bool discard = false;
  char modifier = '\0';
  char type = '\0';

  bool have_more = true;

  while (i < n)
    {
      have_more = true;

      if (! buf)
        buf = new std::ostringstream ();

      if (s[i] == '%')
        {
          // Process percent-escape conversion type.

          process_conversion (s, i, n, width, discard, type, modifier);

          have_more = (buf != 0);
        }
      else if (isspace (s[i]))
        {
          type = scanf_format_elt::whitespace_conversion;

          width = 0;
          discard = false;
          modifier = '\0';
          *buf << " ";

          while (++i < n && isspace (s[i]))
            ; // skip whitespace

          add_elt_to_list (width, discard, type, modifier);

          have_more = false;
        }
      else
        {
          type = scanf_format_elt::literal_conversion;

          width = 0;
          discard = false;
          modifier = '\0';

          while (i < n && ! isspace (s[i]) && s[i] != '%')
            *buf << s[i++];

          add_elt_to_list (width, discard, type, modifier);

          have_more = false;
        }

      if (nconv < 0)
        {
          have_more = false;
          break;
        }
    }

  if (have_more)
    add_elt_to_list (width, discard, type, modifier);

  delete buf;
}

scanf_format_list::~scanf_format_list (void)
{
  size_t n = fmt_elts.size ();

  for (size_t i = 0; i < n; i++)
    {
      scanf_format_elt *elt = fmt_elts[i];
      delete elt;
    }
}

void
scanf_format_list::add_elt_to_list (int width, bool discard, char type,
                                    char modifier,
                                    const std::string& char_class)
{
  if (buf)
    {
      std::string text = buf->str ();

      if (! text.empty ())
        {
          scanf_format_elt *elt
            = new scanf_format_elt (text.c_str (), width, discard, type,
                                    modifier, char_class);

          fmt_elts.push_back (elt);
        }

      delete buf;
      buf = 0;
    }
}

static std::string
expand_char_class (const std::string& s)
{
  std::string retval;

  size_t len = s.length ();

  size_t i = 0;

  while (i < len)
    {
      unsigned char c = s[i++];

      if (c == '-' && i > 1 && i < len
          && (   static_cast<unsigned char> (s[i-2])
              <= static_cast<unsigned char> (s[i])))
        {
          // Add all characters from the range except the first (we
          // already added it below).

          for (c = s[i-2]+1; c < s[i]; c++)
            retval += c;
        }
      else
        {
          // Add the character to the class.  Only add '-' if it is
          // the last character in the class.

          if (c != '-' || i == len)
            retval += c;
        }
    }

  return retval;
}

void
scanf_format_list::process_conversion (const std::string& s, size_t& i,
                                       size_t n, int& width, bool& discard,
                                       char& type, char& modifier)
{
  width = 0;
  discard = false;
  modifier = '\0';
  type = '\0';

  *buf << s[i++];

  bool have_width = false;

  while (i < n)
    {
      switch (s[i])
        {
        case '*':
          if (discard)
            nconv = -1;
          else
            {
              discard = true;
              *buf << s[i++];
            }
          break;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          if (have_width)
            nconv = -1;
          else
            {
              char c = s[i++];
              width = 10 * width + c - '0';
              have_width = true;
              *buf << c;
              while (i < n && isdigit (s[i]))
                {
                  c = s[i++];
                  width = 10 * width + c - '0';
                  *buf << c;
                }
            }
          break;

        case 'h': case 'l': case 'L':
          if (modifier != '\0')
            nconv = -1;
          else
            modifier = s[i++];
          break;

        case 'd': case 'i': case 'o': case 'u': case 'x':
          if (modifier == 'L')
            {
              nconv = -1;
              break;
            }
          goto fini;

        case 'e': case 'f': case 'g':
          if (modifier == 'h')
            {
              nconv = -1;
              break;
            }

          // No float or long double conversions, thanks.
          *buf << 'l';

          goto fini;

        case 'c': case 's': case 'p': case '%': case '[':
          if (modifier != '\0')
            {
              nconv = -1;
              break;
            }
          goto fini;

        fini:
          {
            if (finish_conversion (s, i, n, width, discard,
                                   type, modifier) == 0)
              return;
          }
          break;

        default:
          nconv = -1;
          break;
        }

      if (nconv < 0)
        break;
    }

  nconv = -1;
}

int
scanf_format_list::finish_conversion (const std::string& s, size_t& i,
                                      size_t n, int& width, bool discard,
                                      char& type, char modifier)
{
  int retval = 0;

  std::string char_class;

  size_t beg_idx = std::string::npos;
  size_t end_idx = std::string::npos;

  if (s[i] == '%')
    {
      type = '%';
      *buf << s[i++];
    }
  else
    {
      type = s[i];

      if (s[i] == '[')
        {
          *buf << s[i++];

          if (i < n)
            {
              beg_idx = i;

              if (s[i] == '^')
                {
                  type = '^';
                  *buf << s[i++];

                  if (i < n)
                    {
                      beg_idx = i;

                      if (s[i] == ']')
                        *buf << s[i++];
                    }
                }
              else if (s[i] == ']')
                *buf << s[i++];
            }

          while (i < n && s[i] != ']')
            *buf << s[i++];

          if (i < n && s[i] == ']')
            {
              end_idx = i-1;
              *buf << s[i++];
            }

          if (s[i-1] != ']')
            retval = nconv = -1;
        }
      else
        *buf << s[i++];

      nconv++;
    }

  if (nconv >= 0)
    {
      if (beg_idx != std::string::npos && end_idx != std::string::npos)
        char_class = expand_char_class (s.substr (beg_idx,
                                        end_idx - beg_idx + 1));

      add_elt_to_list (width, discard, type, modifier, char_class);
    }

  return retval;
}

void
scanf_format_list::printme (void) const
{
  size_t n = fmt_elts.size ();

  for (size_t i = 0; i < n; i++)
    {
      scanf_format_elt *elt = fmt_elts[i];

      std::cerr
        << "width:      " << elt->width << "\n"
        << "discard:    " << elt->discard << "\n"
        << "type:       ";

      if (elt->type == scanf_format_elt::literal_conversion)
        std::cerr << "literal text\n";
      else if (elt->type == scanf_format_elt::whitespace_conversion)
        std::cerr << "whitespace\n";
      else
        std::cerr << elt->type << "\n";

      std::cerr
        << "modifier:   " << elt->modifier << "\n"
        << "char_class: '" << undo_string_escapes (elt->char_class) << "'\n"
        << "text:       '" << undo_string_escapes (elt->text) << "'\n\n";
    }
}

bool
scanf_format_list::all_character_conversions (void)
{
  size_t n = fmt_elts.size ();

  if (n > 0)
    {
      for (size_t i = 0; i < n; i++)
        {
          scanf_format_elt *elt = fmt_elts[i];

          switch (elt->type)
            {
            case 'c': case 's': case '%': case '[': case '^':
            case scanf_format_elt::literal_conversion:
            case scanf_format_elt::whitespace_conversion:
              break;

            default:
              return false;
              break;
            }
        }

      return true;
    }
  else
    return false;
}

bool
scanf_format_list::all_numeric_conversions (void)
{
  size_t n = fmt_elts.size ();

  if (n > 0)
    {
      for (size_t i = 0; i < n; i++)
        {
          scanf_format_elt *elt = fmt_elts[i];

          switch (elt->type)
            {
            case 'd': case 'i': case 'o': case 'u': case 'x':
            case 'e': case 'f': case 'g':
              break;

            default:
              return false;
              break;
            }
        }

      return true;
    }
  else
    return false;
}

class
printf_format_elt
{
public:

  printf_format_elt (const char *txt = 0, int n = 0, int w = -1,
                     int p = -1, const std::string& f = "",
                     char typ = '\0', char mod = '\0')
    : text (strsave (txt)), args (n), fw (w), prec (p), flags (f),
      type (typ), modifier (mod)
  { }

  printf_format_elt (const printf_format_elt& e)
    : text (strsave (e.text)), args (e.args), fw (e.fw), prec (e.prec),
      flags (e.flags), type (e.type), modifier (e.modifier)
  { }

  printf_format_elt& operator = (const printf_format_elt& e)
  {
    if (this != &e)
      {
        text = strsave (e.text);
        args = e.args;
        fw = e.fw;
        prec = e.prec;
        flags = e.flags;
        type = e.type;
        modifier = e.modifier;
      }

    return *this;
  }

  ~printf_format_elt (void) { delete [] text; }

  // The C-style format string.
  const char *text;

  // How many args do we expect to consume?
  int args;

  // Field width.
  int fw;

  // Precision.
  int prec;

  // Flags -- '-', '+', ' ', '0', or '#'.
  std::string flags;

  // Type of conversion -- 'd', 'i', 'o', 'x', 'X', 'u', 'c', 's',
  // 'f', 'e', 'E', 'g', 'G', 'p', or '%'
  char type;

  // A length modifier -- 'h', 'l', or 'L'.
  char modifier;
};

class
printf_format_list
{
public:

  printf_format_list (const std::string& fmt = "");

  ~printf_format_list (void);

  octave_idx_type num_conversions (void) { return nconv; }

  const printf_format_elt *first (void)
  {
    curr_idx = 0;
    return current ();
  }

  const printf_format_elt *current (void) const
  {
    return length () > 0 ? fmt_elts[curr_idx] : 0;
  }

  size_t length (void) const { return fmt_elts.size (); }

  const printf_format_elt *next (bool cycle = true)
  {
    curr_idx++;

    if (curr_idx >= length ())
      {
        if (cycle)
          curr_idx = 0;
        else
          return 0;
      }

    return current ();
  }

  bool last_elt_p (void) { return (curr_idx + 1 == length ()); }

  void printme (void) const;

  bool ok (void) const { return (nconv >= 0); }

  operator bool () const { return ok (); }

private:

  // Number of conversions specified by this format string, or -1 if
  // invalid conversions have been found.
  octave_idx_type nconv;

  // Index to current element;
  size_t curr_idx;

  // List of format elements.
  std::deque<printf_format_elt*> fmt_elts;

  // Temporary buffer.
  std::ostringstream *buf;

  void add_elt_to_list (int args, const std::string& flags, int fw,
                        int prec, char type, char modifier);

  void process_conversion (const std::string& s, size_t& i, size_t n,
                           int& args, std::string& flags, int& fw,
                           int& prec, char& modifier, char& type);

  void finish_conversion (const std::string& s, size_t& i, int args,
                          const std::string& flags, int fw, int prec,
                          char modifier, char& type);

  // No copying!

  printf_format_list (const printf_format_list&);

  printf_format_list& operator = (const printf_format_list&);
};

printf_format_list::printf_format_list (const std::string& s)
  : nconv (0), curr_idx (0), fmt_elts (), buf (0)
{
  size_t n = s.length ();

  size_t i = 0;

  int args = 0;
  std::string flags;
  int fw = -1;
  int prec = -1;
  char modifier = '\0';
  char type = '\0';

  bool have_more = true;
  bool empty_buf = true;

  if (n == 0)
    {
      printf_format_elt *elt
        = new printf_format_elt ("", args, fw, prec, flags, type, modifier);

      fmt_elts.push_back (elt);
    }
  else
    {
      while (i < n)
        {
          have_more = true;

          if (! buf)
            {
              buf = new std::ostringstream ();
              empty_buf = true;
            }

          switch (s[i])
            {
            case '%':
              {
                if (empty_buf)
                  {
                    process_conversion (s, i, n, args, flags, fw, prec,
                                        type, modifier);

                    have_more = (buf != 0);
                  }
                else
                  add_elt_to_list (args, flags, fw, prec, type, modifier);
              }
              break;

            default:
              {
                args = 0;
                flags = "";
                fw = -1;
                prec = -1;
                modifier = '\0';
                type = '\0';
                *buf << s[i++];
                empty_buf = false;
              }
              break;
            }

          if (nconv < 0)
            {
              have_more = false;
              break;
            }
        }

      if (have_more)
        add_elt_to_list (args, flags, fw, prec, type, modifier);

      delete buf;
    }
}

printf_format_list::~printf_format_list (void)
{
  size_t n = fmt_elts.size ();

  for (size_t i = 0; i < n; i++)
    {
      printf_format_elt *elt = fmt_elts[i];
      delete elt;
    }
}

void
printf_format_list::add_elt_to_list (int args, const std::string& flags,
                                     int fw, int prec, char type,
                                     char modifier)
{
  if (buf)
    {
      std::string text = buf->str ();

      if (! text.empty ())
        {
          printf_format_elt *elt
            = new printf_format_elt (text.c_str (), args, fw, prec, flags,
                                     type, modifier);

          fmt_elts.push_back (elt);
        }

      delete buf;
      buf = 0;
    }
}

void
printf_format_list::process_conversion (const std::string& s, size_t& i,
                                        size_t n, int& args,
                                        std::string& flags, int& fw,
                                        int& prec, char& modifier,
                                        char& type)
{
  args = 0;
  flags = "";
  fw = -1;
  prec = -1;
  modifier = '\0';
  type = '\0';

  *buf << s[i++];

  bool nxt = false;

  while (i < n)
    {
      switch (s[i])
        {
        case '-': case '+': case ' ': case '0': case '#':
          flags += s[i];
          *buf << s[i++];
          break;

        default:
          nxt = true;
          break;
        }

      if (nxt)
        break;
    }

  if (i < n)
    {
      if (s[i] == '*')
        {
          fw = -2;
          args++;
          *buf << s[i++];
        }
      else
        {
          if (isdigit (s[i]))
            {
              int nn = 0;
              std::string tmp = s.substr (i);
              sscanf (tmp.c_str (), "%d%n", &fw, &nn);
            }

          while (i < n && isdigit (s[i]))
            *buf << s[i++];
        }
    }

  if (i < n && s[i] == '.')
    {
      // nothing before the . means 0.
      if (fw == -1)
        fw = 0;

      // . followed by nothing is 0.
      prec = 0;

      *buf << s[i++];

      if (i < n)
        {
          if (s[i] == '*')
            {
              prec = -2;
              args++;
              *buf << s[i++];
            }
          else
            {
              if (isdigit (s[i]))
                {
                  int nn = 0;
                  std::string tmp = s.substr (i);
                  sscanf (tmp.c_str (), "%d%n", &prec, &nn);
                }

              while (i < n && isdigit (s[i]))
                *buf << s[i++];
            }
        }
    }

  if (i < n)
    {
      // Accept and record modifier, but don't place it in the format
      // item text.  All integer conversions are handled as 64-bit
      // integers.

      switch (s[i])
        {
        case 'h': case 'l': case 'L':
          modifier = s[i++];
          break;

        default:
          break;
        }
    }

  if (i < n)
    finish_conversion (s, i, args, flags, fw, prec, modifier, type);
  else
    nconv = -1;
}

void
printf_format_list::finish_conversion (const std::string& s, size_t& i,
                                       int args, const std::string& flags,
                                       int fw, int prec, char modifier,
                                       char& type)
{
  switch (s[i])
    {
    case 'd': case 'i': case 'o': case 'x': case 'X':
    case 'u': case 'c':
      if (modifier == 'L')
        {
          nconv = -1;
          break;
        }
      goto fini;

    case 'f': case 'e': case 'E': case 'g': case 'G':
      if (modifier == 'h' || modifier == 'l')
        {
          nconv = -1;
          break;
        }
      goto fini;

    case 's': case 'p': case '%':
      if (modifier != '\0')
        {
          nconv = -1;
          break;
        }
      goto fini;

    fini:

      type = s[i];

      *buf << s[i++];

      if (type != '%' || args != 0)
        nconv++;

      if (type != '%')
        args++;

      add_elt_to_list (args, flags, fw, prec, type, modifier);

      break;

    default:
      nconv = -1;
      break;
    }
}

void
printf_format_list::printme (void) const
{
  size_t n = fmt_elts.size ();

  for (size_t i = 0; i < n; i++)
    {
      printf_format_elt *elt = fmt_elts[i];

      std::cerr
        << "args:     " << elt->args << "\n"
        << "flags:    '" << elt->flags << "'\n"
        << "width:    " << elt->fw << "\n"
        << "prec:     " << elt->prec << "\n"
        << "type:     '" << elt->type << "'\n"
        << "modifier: '" << elt->modifier << "'\n"
        << "text:     '" << undo_string_escapes (elt->text) << "'\n\n";
    }
}

// Delimited stream, optimised to read strings of characters separated
// by single-character delimiters.
//
// The reason behind this class is that octstream doesn't provide
// seek/tell, but the opportunity has been taken to optimise for the
// textscan workload.
//
// The function reads chunks into a 4kiB buffer, and marks where the
// last delimiter occurs.  Reads up to this delimiter can be fast.
// After that last delimiter, the remaining text is moved to the front
// of the buffer and the buffer is refilled.  This also allows cheap
// seek and tell operations within a "fast read" block.

class
delimited_stream
{
public:

  delimited_stream (std::istream& is, const std::string& delimiters,
        int longest_lookahead, octave_idx_type bsize = 4096);

  delimited_stream (std::istream& is, const delimited_stream& ds);

  ~delimited_stream (void);

  // Called when optimised sequence of get is finished.  Ensures that
  // there is a remaining delimiter in buf, or loads more data in.
  void field_done (void)
  {
    if (idx >= last)
      refresh_buf ();
  }

  // Load new data into buffer, and set eob, last, idx.
  // Return EOF at end of file, 0 otherwise.
  int refresh_buf (void);

  // Get a character, relying on caller to call field_done if
  // a delimiter has been reached.
  int get (void)
  {
    if (delimited)
      return eof () ? std::istream::traits_type::eof () : *idx++;
    else
      return get_undelim ();
  }

  // Get a character, checking for underrun of the buffer.
  int get_undelim (void);

  // Read character that will be got by the next get.
  int peek (void) { return eof () ? std::istream::traits_type::eof () : *idx; }

  // Read character that will be got by the next get.
  int peek_undelim (void);

  // Undo a 'get' or 'get_undelim'.  It is the caller's responsibility
  // to avoid overflow by calling putbacks only for a character got by
  // get() or get_undelim(), with no intervening
  // get, get_delim, field_done, refresh_buf, getline, read or seekg.
  void putback (char /*ch*/ = 0) { if (! eof ()) --idx; }

  int getline  (std::string& dest, char delim);

  // int skipline (char delim);

  char *read (char *buffer, int size, char* &new_start);

  // Return a position suitable to "seekg", valid only within this
  // block between calls to field_done.
  char *tellg (void) { return idx; }

  void seekg (char *old_idx) { idx = old_idx; }

  bool eof (void)
  {
    return (eob == buf && i_stream.eof ()) || (flags & std::ios_base::eofbit);
  }

  operator const void* (void) { return (! eof () && ! flags) ? this : 0; }

  bool fail (void) { return flags & std::ios_base::failbit; }

  std::ios_base::iostate rdstate (void) { return flags; }

  void setstate (std::ios_base::iostate m) { flags = flags | m; }

  void clear (std::ios_base::iostate m
              = (std::ios_base::eofbit & ~std::ios_base::eofbit))
  {
    flags = flags & m;
  }

  // Report if any characters have been consumed.
  // (get, read etc. not cancelled by putback or seekg)

  void progress_benchmark (void) { progress_marker = idx; }

  bool no_progress (void) { return progress_marker == idx; }

private:

  // Number of characters to read from the file at once.
  int bufsize;

  // Stream to read from.
  std::istream& i_stream;

  // Temporary storage for a "chunk" of data.
  char *buf;

  // Current read pointer.
  char *idx;

  // Location of last delimiter in the buffer at buf (undefined if
  // delimited is false).
  char *last;

  // Position after last character in buffer.
  char *eob;

  // True if there is delimiter in the bufer after idx.
  bool delimited;

  // Longest lookahead required.
  int longest;

  // Sequence of single-character delimiters.
  const std::string delims;

  // Position of start of buf in original stream.
  std::streampos buf_in_file;

  // Marker to see if a read consumes any characters.
  char *progress_marker;

  std::ios_base::iostate flags;

  // No copying!

  delimited_stream (const delimited_stream&);

  delimited_stream& operator = (const delimited_stream&);
};

// Create a delimited stream, reading from is, with delimiters delims,
// and allowing reading of up to tellg + longest_lookeahead.  When is
// is at EOF, lookahead may be padded by ASCII nuls.

delimited_stream::delimited_stream (std::istream& is,
                                    const std::string& delimiters,
                                    int longest_lookahead,
                                    octave_idx_type bsize)
  : bufsize (bsize), i_stream (is), longest (longest_lookahead),
    delims (delimiters),
    flags (std::ios::failbit & ~std::ios::failbit) // can't cast 0
{
  buf = new char[bufsize];
  eob = buf + bufsize;
  idx = eob;                    // refresh_buf shouldn't try to copy old data
  progress_marker = idx;
  refresh_buf ();               // load the first batch of data
}

// Used to create a stream from a strstream from data read from a dstr.
// FIXME: Find a more efficient approach.  Perhaps derived dstr
delimited_stream::delimited_stream (std::istream& is,
                                    const delimited_stream& ds)
  : bufsize (ds.bufsize), i_stream (is), longest (ds.longest),
    delims (ds.delims),
    flags (std::ios::failbit & ~std::ios::failbit) // can't cast 0
{
  buf = new char[bufsize];
  eob = buf + bufsize;
  idx = eob;                    // refresh_buf shouldn't try to copy old data
  progress_marker = idx;
  refresh_buf ();               // load the first batch of data
}

delimited_stream::~delimited_stream (void)
{
  // Seek to the correct position in i_stream.
  if (! eof ())
    {
      i_stream.clear ();
      i_stream.seekg (buf_in_file);
      i_stream.read (buf, idx - buf);
    }

  delete [] buf;
}

// Read a character from the buffer, refilling the buffer from the file
// if necessary.

int
delimited_stream::get_undelim (void)
{
  int retval;
  if (eof ())
    {
      setstate (std::ios_base::failbit);
      return std::istream::traits_type::eof ();
    }

  if (idx < eob)
    retval = *idx++;
  else
    {
      refresh_buf ();

      if (eof ())
        {
          setstate (std::ios_base::eofbit);
          retval = std::istream::traits_type::eof ();
        }
      else
        retval = *idx++;
    }

  if (idx >= last)
    delimited = false;

  return retval;
}

// Return the next character to be read without incrementing the
// pointer, refilling the buffer from the file if necessary.

int
delimited_stream::peek_undelim (void)
{
  int retval = get_undelim ();
  putback ();

  return retval;
}

// Copy remaining unprocessed data to the start of the buffer and load
// new data to fill it.  Return EOF if the file is at EOF before
// reading any data and all of the data that has been read has been
// processed.

int
delimited_stream::refresh_buf (void)
{
  if (eof ())
    return std::istream::traits_type::eof ();

  int retval;

  if (eob < idx)
    idx = eob;

  size_t old_remaining = eob - idx;

  octave_quit ();                       // allow ctrl-C

  if (old_remaining > 0)
    memmove (buf, idx, old_remaining);

  progress_marker -= idx - buf;         // where original idx would have been
  idx = buf;

  int gcount;   // chars read
  if (! i_stream.eof ())
    {
      buf_in_file = i_stream.tellg ();   // record for destructor
      i_stream.read (buf + old_remaining, bufsize - old_remaining);
      gcount = i_stream.gcount ();
    }
  else
    gcount = 0;

  eob = buf + old_remaining + gcount;
  last = eob;
  if (gcount == 0)
    {
      delimited = false;

      if (eob != buf)              // no more data in file, but still some to go
        retval = 0;
      else
        // file and buffer are both done.
        retval = std::istream::traits_type::eof ();
    }
  else
    {
      delimited = true;

      for (last = eob - longest; last - buf >= 0; last--)
        {
          if (delims.find (*last) != std::string::npos)
            break;
        }

      if (last < buf)
        delimited = false;

      retval = 0;
    }

  // Ensure fast peek doesn't give valid char
  if (retval == std::istream::traits_type::eof ())
    *idx = '\0';      // FIXME - check that no TreatAsEmpty etc starts w. \0?

  return retval;
}

// Return a pointer to a block of data of size size, assuming that a
// sufficiently large buffer is available in buffer, if required.
// If called when delimited == true, and size is no greater than
// longest_lookahead then this will not call refresh_buf, so seekg
// still works.  Otherwise, seekg may be invalidated.

char *
delimited_stream::read (char *buffer, int size, char* &prior_tell)
{
  char *retval;

  if (eob  - idx > size)
    {
      retval = idx;
      idx += size;
      if (idx > last)
        delimited = false;
    }
  else
    {
      // If there was a tellg pointing to an earlier point than the current
      // read position, try to keep it in the active buffer.
      // In the current code, prior_tell==idx for each call,
      // so this is not necessary, just a precaution.

      if (eob - prior_tell + size < bufsize)
        {
          octave_idx_type gap = idx - prior_tell;
          idx = prior_tell;
          refresh_buf ();
          idx += gap;
        }
      else      // can't keep the tellg in range.  May skip some data.
        {
          refresh_buf ();
        }

      prior_tell = buf;

      if (eob - idx > size)
        {
          retval = idx;
          idx += size;
          if (idx > last)
            delimited = false;
        }
      else
        {
          if (size <= bufsize)          // small read, but reached EOF
            {
              retval = idx;
              memset (eob, 0, size + (idx - buf));
              idx += size;
            }
          else  // Reading more than the whole buf; return it in buffer
            {
              retval = buffer;
              // FIXME -- read bufsize at a time
              int i;
              for (i = 0; i < size && ! eof (); i++)
                *buffer++ = get_undelim ();
              if (eof ())
                memset (buffer, 0, size - i);
            }
        }
    }

  return retval;
}

// Return in OUT an entire line, terminated by delim.  On input, OUT
// must have length at least 1.

int
delimited_stream::getline (std::string& out, char delim)
{
  int len = out.length (), used = 0;
  int ch;
  while ((ch = get_undelim ()) != delim
         && ch != std::istream::traits_type::eof ())
    {
      out[used++] = ch;
      if (used == len)
        {
          len <<= 1;
          out.resize (len);
        }
    }
  out.resize (used);
  field_done ();

  return ch;
}

// A single conversion specifier, such as %f or %c.

class
textscan_format_elt
{
public:

  enum special_conversion
  {
    whitespace_conversion = 1,
    literal_conversion = 2
  };

  textscan_format_elt (const std::string& txt, int w = 0, int p = -1,
                       int bw = 0, bool dis = false, char typ = '\0',
                       const std::string& ch_class = std::string ())
    : text (txt), width (w), prec (p), bitwidth (bw),
      char_class (ch_class), type (typ), discard (dis),
      numeric (typ == 'd' || typ == 'u' || type == 'f' || type == 'n')
  { }

  textscan_format_elt (const textscan_format_elt& e)
    : text (e.text), width (e.width), prec (e.prec),
      bitwidth (e.bitwidth), char_class (e.char_class), type (e.type),
      discard (e.discard), numeric (e.numeric)
  { }

  textscan_format_elt& operator = (const textscan_format_elt& e)
  {
    if (this != &e)
      {
        text = e.text;
        width = e.width;
        prec = e.prec;
        bitwidth = e.bitwidth;
        discard = e.discard;
        type = e.type;
        numeric = e.numeric;
        char_class = e.char_class;
      }

    return *this;
  }

  // The C-style format string.
  std::string text;

  // The maximum field width.
  unsigned int width;

  // The maximum number of digits to read after the decimal in a
  // floating point conversion.
  int prec;

  // The size of the result.  For integers, bitwidth may be 8, 16, 34,
  // or 64.  For floating point values, bitwidth may be 32 or 64.
  int bitwidth;

  // The class of characters in a `[' or `^' format.
  std::string char_class;

  // Type of conversion
  //  -- `d', `u', `f', `n', `s', `q', `c', `%', `C', `D', `[' or `^'.
  char type;

  // TRUE if we are not storing the result of this conversion.
  bool discard;

  // TRUE if the type is 'd', 'u', 'f', 'n'
  bool numeric;
};

class textscan;

// The (parsed) sequence of format specifiers.

class
textscan_format_list
{
public:

  textscan_format_list (const std::string& fmt = std::string ());

  ~textscan_format_list (void);

  octave_idx_type num_conversions (void) const { return nconv; }

  // The length can be different than the number of conversions.
  // For example, "x %d y %d z" has 2 conversions but the length of
  // the list is 3 because of the characters that appear after the
  // last conversion.

  size_t numel (void) const { return fmt_elts.size (); }

  const textscan_format_elt *first (void)
  {
    curr_idx = 0;
    return current ();
  }

  const textscan_format_elt *current (void) const
  {
    return numel () > 0 ? fmt_elts[curr_idx] : 0;
  }

  const textscan_format_elt *next (bool cycle = true)
  {
    curr_idx++;

    if (curr_idx >= numel ())
      {
        if (cycle)
          curr_idx = 0;
        else
          return 0;
      }

    return current ();
  }

  void printme (void) const;

  bool ok (void) const { return (nconv >= 0); }

  operator const void* (void) const { return ok () ? this : 0; }

  // True if number of %f to be set from data file.
  bool set_from_first;

  // At least one conversion specifier is s,q,c, or [...].
  bool has_string;

  int read_first_row (delimited_stream& is, textscan& ts);

  std::list<octave_value> out_buf (void) const { return (output_container); }

private:

  // Number of conversions specified by this format string, or -1 if
  // invalid conversions have been found.
  octave_idx_type nconv;

  // Index to current element;
  size_t curr_idx;

  // List of format elements.
  std::deque<textscan_format_elt*> fmt_elts;

  // list holding column arrays of types specified by conversions
  std::list<octave_value> output_container;

  // Temporary buffer.
  std::ostringstream *buf;

  void add_elt_to_list (unsigned int width, int prec, int bitwidth,
                        octave_value val_type, bool discard,
                        char type,
                        const std::string& char_class = std::string ());

  void process_conversion (const std::string& s, size_t& i, size_t n);

  int finish_conversion (const std::string& s, size_t& i, size_t n,
                         unsigned int& width, int& prec, int& bitwidth,
                         octave_value& val_type,
                         bool discard, char& type);
  // No copying!

  textscan_format_list (const textscan_format_list&);

  textscan_format_list& operator = (const textscan_format_list&);
};


textscan_format_list::textscan_format_list (const std::string& s)
  : set_from_first (false), has_string (false), nconv (0), curr_idx (0),
    fmt_elts (), buf (0)
{
  size_t n = s.length ();

  size_t i = 0;

  unsigned int width = -1;              // Unspecified width = max (except %c)
  int prec = -1;
  int bitwidth = 0;
  bool discard = false;
  char type = '\0';

  bool have_more = true;

  if (s.empty ())
    {
      buf = new std::ostringstream ("%f");
      bitwidth = 64;
      type = 'f';
      add_elt_to_list (width, prec, bitwidth, octave_value (NDArray ()),
                       discard, type);
      have_more = false;
      set_from_first = true;
      nconv = 1;
    }
  else
    {
      set_from_first = false;

      while (i < n)
        {
          have_more = true;

          if (! buf)
            buf = new std::ostringstream ();

          if (s[i] == '%' && (i+1 == n || s[i+1] != '%'))
            {
              // Process percent-escape conversion type.

              process_conversion (s, i, n);

              have_more = (buf != 0);
            }
          else if (isspace (s[i]))
            {
              while (++i < n && isspace (s[i]))
                /* skip whitespace */;

              have_more = false;
            }
          else
            {
              type = textscan_format_elt::literal_conversion;

              width = 0;
              prec = -1;
              bitwidth = 0;
              discard = true;

              while (i < n && ! isspace (s[i])
                     && (s[i] != '%' || (i+1 < n && s[i+1] == '%')))
                {
                  if (s[i] == '%')      // if double %, skip one
                    i++;
                  *buf << s[i++];
                  width++;
                }

              add_elt_to_list (width, prec, bitwidth, octave_value (),
                               discard, type);

              have_more = false;
            }

          if (nconv < 0)
            {
              have_more = false;
              break;
            }
        }
    }

  if (have_more)
    add_elt_to_list (width, prec, bitwidth, octave_value (), discard, type);

  delete buf;
}

textscan_format_list::~textscan_format_list (void)
{
  size_t n = numel ();

  for (size_t i = 0; i < n; i++)
    {
      textscan_format_elt *elt = fmt_elts[i];
      delete elt;
    }
}

void
textscan_format_list::add_elt_to_list (unsigned int width, int prec,
                                       int bitwidth, octave_value val_type,
                                       bool discard, char type,
                                       const std::string& char_class)
{
  if (buf)
    {
      std::string text = buf->str ();

      if (! text.empty ())
        {
          textscan_format_elt *elt
            = new textscan_format_elt (text, width, prec, bitwidth, discard, type, char_class);

          if (! discard)
            output_container.push_back (val_type);

          fmt_elts.push_back (elt);
        }

      delete buf;
      buf = 0;
    }
}

void
textscan_format_list::process_conversion (const std::string& s, size_t& i,
                                          size_t n)
{
  unsigned width = 0;
  int prec = -1;
  int bitwidth = 0;
  bool discard = false;
  octave_value val_type;
  char type = '\0';

  *buf << s[i++];

  bool have_width = false;

  while (i < n)
    {
      switch (s[i])
        {
        case '*':
          if (discard)
            nconv = -1;
          else
            {
              discard = true;
              *buf << s[i++];
            }
          break;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          if (have_width)
            nconv = -1;
          else
            {
              char c = s[i++];
              width = width * 10 + c - '0';
              have_width = true;
              *buf << c;
              while (i < n && isdigit (s[i]))
                {
                  c = s[i++];
                  width = width * 10 + c - '0';
                  *buf << c;
                }

              if (i < n && s[i] == '.')
                {
                  *buf << s[i++];
                  prec = 0;
                  while (i < n && isdigit (s[i]))
                    {
                      c = s[i++];
                      prec = prec * 10 + c - '0';
                      *buf << c;
                    }
                }
            }
          break;

        case 'd': case 'u':
          {
            bool done = true;
            *buf << (type = s[i++]);
            if (i < n)
              {
                if (s[i] == '8')
                  {
                    bitwidth = 8;
                    if (type == 'd')
                      val_type = octave_value (int8NDArray ());
                    else
                      val_type = octave_value (uint8NDArray ());
                    *buf << s[i++];
                  }
                else if (s[i] == '1' && i+1 < n && s[i+1] == '6')
                  {
                    bitwidth = 16;
                    if (type == 'd')
                      val_type = octave_value (int16NDArray ());
                    else
                      val_type = octave_value (uint16NDArray ());
                    *buf << s[i++];
                    *buf << s[i++];
                  }
                else if (s[i] == '3' && i+1 < n && s[i+1] == '2')
                  {
                    done = false;       // use default size below
                    *buf << s[i++];
                    *buf << s[i++];
                  }
                else if (s[i] == '6' && i+1 < n && s[i+1] == '4')
                  {
                    bitwidth = 64;
                    if (type == 'd')
                      val_type = octave_value (int64NDArray ());
                    else
                      val_type = octave_value (uint64NDArray ());
                    *buf << s[i++];
                    *buf << s[i++];
                  }
                else
                  done = false;
              }
            else
              done = false;

            if (! done)
              {
                bitwidth = 32;
                if (type == 'd')
                  val_type = octave_value (int32NDArray ());
                else
                  val_type = octave_value (uint32NDArray ());
              }
            goto fini;
          }

        case 'f':
          *buf << (type = s[i++]);
          bitwidth = 64;
          if (i < n)
            {
              if (s[i] == '3' && i+1 < n && s[i+1] == '2')
                {
                  bitwidth = 32;
                  val_type = octave_value (FloatNDArray ());
                  *buf << s[i++];
                  *buf << s[i++];
                }
              else if (s[i] == '6' && i+1 < n && s[i+1] == '4')
                {
                  val_type = octave_value (NDArray ());
                  *buf << s[i++];
                  *buf << s[i++];
                }
              else
                val_type = octave_value (NDArray ());
            }
          else
            val_type = octave_value (NDArray ());
          goto fini;

        case 'n':
          *buf << (type = s[i++]);
          bitwidth = 64;
          val_type = octave_value (NDArray ());
          goto fini;

        case 's': case 'q': case '[': case 'c':
          if (! discard)
            val_type = octave_value (Cell ());
          *buf << (type = s[i++]);
          has_string = true;
          goto fini;

        fini:
          {
            if (! have_width)
              {
                if (type == 'c')        // %c defaults to one character
                  width = 1;
                else
                  width = static_cast<unsigned int> (-1); // others: unlimited
              }

            if (finish_conversion (s, i, n, width, prec, bitwidth, val_type,
                                   discard, type) == 0)
              return;
          }
          break;

        default:
          error ("textscan: '%%%c' is not a valid format specifier", s[i]);
        }

      if (nconv < 0)
        break;
    }

  nconv = -1;
}

// Parse [...] and [^...]
//
// Matlab does not expand expressions like A-Z, but they are useful, and
// so we parse them "carefully".  We treat '-' as a usual character
// unless both start and end characters are from the same class (upper
// case, lower case, numeric), or this is not the first '-' in the
// pattern.
//
// Keep both a running list of characters and a mask of which chars have
// occurred.  The first is efficient for patterns with few characters.
// The latter is efficient for [^...] patterns.

static std::string
textscan_char_class (const std::string& pattern)
{
  int len = pattern.length ();
  if (len == 0)
    return "";

  std::string retval (256, '\0');
  std::string mask   (256, '\0');       // number of times chr has been seen

  int in = 0, out = 0;
  unsigned char ch, prev = 0;
  bool flip = false;

  ch = pattern[in];
  if (ch == '^')
    {
      in++;
      flip = true;
    }
  mask[pattern[in]] = '\1';
  retval[out++] = pattern[in++];        // even copy ']' if it is first

  bool prev_was_range = false;          // disallow "a-m-z" as a pattern
  bool prev_prev_was_range = false;
  for (; in < len; in++)
    {
      bool was_range = false;
      ch = pattern[in];
      if (ch == ']')
        break;

      if (prev == '-' && in > 1 && isalnum (ch) && ! prev_prev_was_range)
        {
          unsigned char start_of_range = pattern[in-2];
          if (start_of_range < ch
              && ((isupper (ch) && isupper (start_of_range))
                  || (islower (ch) && islower (start_of_range))
                  || (isdigit (ch) && isdigit (start_of_range))
                  || mask['-'] > 1))    // not the first '-'
            {
              was_range = true;
              out--;
              mask['-']--;
              for (int i = start_of_range; i <= ch; i++)
                {
                  if (mask[i] == '\0')
                    {
                      mask[i] = '\1';
                      retval[out++] = i;
                    }
                }
            }
        }
      if (! was_range)
        {
          if (mask[ch]++ == 0)
            retval[out++] = ch;
          else if (ch != '-')
            warning_with_id ("octave:textscan-pattern",
                             "textscan: [...] contains two '%c's", ch);

          if (prev == '-' && mask['-'] >= 2)
            warning_with_id ("octave:textscan-pattern",
                             "textscan: [...] contains two '-'s "
                             "outside range expressions");
        }
      prev = ch;
      prev_prev_was_range = prev_was_range;
      prev_was_range = was_range;
    }

  if (flip)                             // [^...]
    {
      out = 0;
      for (int i = 0; i < 256; i++)
        if (! mask[i])
          retval[out++] = i;
    }

  retval.resize (out);

  return retval;
}

int
textscan_format_list::finish_conversion (const std::string& s, size_t& i,
                                         size_t n, unsigned int& width,
                                         int& prec, int& bitwidth,
                                         octave_value& val_type, bool discard,
                                         char& type)
{
  int retval = 0;

  std::string char_class;

  size_t beg_idx = std::string::npos;
  size_t end_idx = std::string::npos;

  if (type != '%')
    {
      nconv++;
      if (type == '[')
        {
          if (i < n)
            {
              beg_idx = i;

              if (s[i] == '^')
                {
                  type = '^';
                  *buf << s[i++];

                  if (i < n)
                    {
                      beg_idx = i;

                      if (s[i] == ']')
                        *buf << s[i++];
                    }
                }
              else if (s[i] == ']')
                *buf << s[i++];
            }

          while (i < n && s[i] != ']')
            *buf << s[i++];

          if (i < n && s[i] == ']')
            {
              end_idx = i-1;
              *buf << s[i++];
            }

          if (s[i-1] != ']')
            retval = nconv = -1;
        }
    }

  if (nconv >= 0)
    {
      if (beg_idx != std::string::npos && end_idx != std::string::npos)
        char_class = textscan_char_class (s.substr (beg_idx,
                                                    end_idx - beg_idx + 1));

      add_elt_to_list (width, prec, bitwidth, val_type, discard, type,
                       char_class);
    }

  return retval;
}

void
textscan_format_list::printme (void) const
{
  size_t n = numel ();

  for (size_t i = 0; i < n; i++)
    {
      textscan_format_elt *elt = fmt_elts[i];

      std::cerr
        << "width:      " << elt->width << "\n"
        << "digits      " << elt->prec << "\n"
        << "bitwidth:   " << elt->bitwidth << "\n"
        << "discard:    " << elt->discard << "\n"
        << "type:       ";

      if (elt->type == textscan_format_elt::literal_conversion)
        std::cerr << "literal text\n";
      else if (elt->type == textscan_format_elt::whitespace_conversion)
        std::cerr << "whitespace\n";
      else
        std::cerr << elt->type << "\n";

      std::cerr
        << "char_class: `" << undo_string_escapes (elt->char_class) << "'\n"
        << "text:       `" << undo_string_escapes (elt->text) << "'\n\n";
    }
}

// If FORMAT is explicitly "", it is assumed to be "%f" repeated enough
// times to read the first row of the file.  Set it now.

int
textscan_format_list::read_first_row (delimited_stream& is, textscan& ts)
{
  // Read first line and strip end-of-line, which may be two characters
  std::string first_line (20, ' ');

  is.getline (first_line, static_cast<char> (ts.eol2));

  if (! first_line.empty ()
      && first_line[first_line.length () - 1] == ts.eol1)
    first_line.resize (first_line.length () - 1);

  std::istringstream strstr (first_line);
  delimited_stream ds (strstr, is);

  dim_vector dv (1,1);      // initial size of each output_container
  Complex val;
  octave_value val_type;
  nconv = 0;
  int max_empty = 1000;     // failsafe, if ds fails but not with eof
  int retval = 0;

  // read line, creating output_container as we go
  while (! ds.eof ())
    {
      bool already_skipped_delim = false;
      ts.skip_whitespace (ds);
      ds.progress_benchmark ();
      bool progress = false;
      ts.scan_complex (ds, *fmt_elts[0], val);
      if (ds.fail ())
        {
          ds.clear (ds.rdstate () & ~std::ios::failbit);

          if (ds.eof ())
            break;

          // If we don't continue after a conversion error, then
          // unless this was a missing value (i.e., followed by a delimiter),
          // return with an error status.
          if (ts.return_on_error < 2)
            {
              ts.skip_delim (ds);
              if (ds.no_progress ())
                {
                  retval = 4;
                  break;
                }
              already_skipped_delim = true;
            }
          else  // skip offending field
            {
              std::ios::iostate state = ds.rdstate ();
              ds.clear ();          // clear to allow read pointer to advance

              std::string dummy;
              textscan_format_elt fe ("", first_line.length ());
              ts.scan_string (ds, fe, dummy);

              progress = (dummy.length ());
              ds.setstate (state);
            }

          val = ts.empty_value.scalar_value ();

          if (! --max_empty)
            break;
        }

      if (val.imag () == 0)
        val_type = octave_value (NDArray (dv, val.real ()));
      else
        val_type = octave_value (ComplexNDArray (dv, val));

      output_container.push_back (val_type);

      if (! already_skipped_delim)
        ts.skip_delim (ds);

      if (! progress && ds.no_progress ())
        break;

      nconv++;
    }

  output_container.pop_front (); // discard empty element from constructor

  // Create fmt_list now that the size is known
  for (octave_idx_type i = 1; i < nconv; i++)
    fmt_elts.push_back (new textscan_format_elt (*fmt_elts[0]));

  return retval;             // May have returned 4 above.
}

// Perform actual textscan: read data from stream, and create cell array.

static Cell
init_inf_nan (void)
{
  Cell retval (dim_vector (1, 2));

  retval(0) = Cell (octave_value ("inf"));
  retval(1) = Cell (octave_value ("nan"));

  return retval;
}

textscan::textscan (void)
  : buf (), whitespace_table (), delim_table (), delims (),
    comment_style (), comment_len (0), comment_char (-2),
    buffer_size (0), date_locale (), inf_nan (init_inf_nan ()),
    empty_value (octave_NaN), exp_chars ("edED"),
    header_lines (0), treat_as_empty (), treat_as_empty_len (0),
    whitespace (" \b\t"), eol1 ('\r'), eol2 ('\n'),
    return_on_error (2), collect_output (false),
    multiple_delims_as_one (false), default_exp (true),
    numeric_delim (false), lines (0)
{ }

octave_value
textscan::scan (std::istream& isp, const octave_value_list& args)
{
  octave_idx_type count = 0;

  return scan (isp, args, count);
}

octave_value
textscan::scan (std::istream& isp, const octave_value_list& args,
                octave_idx_type& count)
{
  std::string format;
  int params = 0;

  if (args.length () == 0)
    format = "%f";      // ommited format = %f.  explicit "" = width from file
  else if (args(0).is_string ())
    {
      format = args(0).string_value ();

      if (args(0).is_sq_string ())
        format = do_string_escapes (format);

      params++;
    }
  else
    error ("textscan: FORMAT must be a string, not <%s>",
           args(0).class_name ().c_str ());

  octave_idx_type ntimes = -1;

  if (args.length () > 1)
    {
      if (args(1).is_numeric_type ())
        {
          ntimes = args(1).idx_type_value ();

          if (ntimes < args(1).double_value ())
            error ("textscan: REPEAT = %g is too large",
                   args(1).double_value ());

          params++;
        }
    }

  octave_value_list tmp_args = args.splice (0, params);

  textscan_format_list fmt_list (format);

  parse_options (tmp_args, fmt_list);

  octave_value result = do_scan (isp, fmt_list, ntimes);

  // FIXME: this is probably not the best way to get count.  The
  // position could easily be larger than octave_idx_type when using
  // 32-bit indexing.

  std::ios::iostate state = isp.rdstate ();
  isp.clear ();
  count = static_cast<octave_idx_type> (isp.tellg ());
  isp.setstate (state);

  return result;
}

octave_value
textscan::do_scan (std::istream& isp, textscan_format_list& fmt_list,
                   octave_idx_type ntimes)
{
  octave_value retval;

  if (fmt_list.num_conversions () == -1)
    error ("textscan: invalid format specified");

  if (fmt_list.num_conversions () == 0)
    error ("textscan: no valid format conversion specifiers\n");

  // skip the first header_lines
  std::string dummy;
  for (int i = 0; i < header_lines && isp; i++)
    getline (isp, dummy, static_cast<char> (eol2));

  // Create our own buffered stream, for fast get/putback/tell/seek.

  // First, see how far ahead it should let us look.
  int max_lookahead = std::max (std::max (comment_len, treat_as_empty_len),
                                std::max (delim_len, 3)); // 3 for NaN and Inf

  // Next, choose a buffer size to avoid reading too much, or too often.
  octave_idx_type buf_size = 4096;
  if (buffer_size)
    buf_size = buffer_size;
  else if (ntimes > 0)
    {
      // Avoid overflow of 80*ntimes...
      buf_size = std::min (buf_size, std::max (ntimes, 80 * ntimes));
      buf_size = std::max (buf_size, ntimes);
    }
  // Finally, create the stream.
  delimited_stream is (isp, whitespace + delims, max_lookahead, buf_size);

  // Grow retval dynamically.  "size" is half the initial size
  // (FIXME -- Should we start smaller if ntimes is large?)
  octave_idx_type size = ((ntimes < 8 && ntimes >= 0) ? ntimes : 1);
  Array<octave_idx_type> row_idx (dim_vector (1,2));
  row_idx(1) = 0;

  int err = 0;
  octave_idx_type row = 0;

  if (multiple_delims_as_one)           // bug #44750?
    skip_delim (is);

  int done_after;  // Number of columns read when EOF seen.

  // If FORMAT explicitly "", read first line and see how many "%f" match
  if (fmt_list.set_from_first)
    {
      err = fmt_list.read_first_row (is, *this);
      lines = 1;

      done_after = fmt_list.numel () + 1;
      if (! err)
        row = 1;  // the above puts the first line into fmt_list.out_buf ()
    }
  else
    done_after = fmt_list.out_buf ().size () + 1;

  std::list<octave_value> out = fmt_list.out_buf ();

  // We will later merge adjacent columns of the same type.
  // Check now which columns to merge.
  // Reals may become complex, and so we can't trust types
  // after reading in data.
  // If the format was "", that conversion may already have happened,
  // so force all to be merged (as all are %f).
  bool merge_with_prev[fmt_list.numel ()];
  int conv = 0;
  if (collect_output)
    {
      int prev_type = -1;
      for (std::list<octave_value>::iterator col = out.begin ();
           col != out.end (); col++)
        {
          if (col->type_id () == prev_type
              || (fmt_list.set_from_first && prev_type != -1))
            merge_with_prev [conv++] = true;
          else
            merge_with_prev [conv++] = false;

          prev_type = col->type_id ();
        }
    }

  // This should be caught by earlier code, but this avoids a possible
  // infinite loop below.
  if (fmt_list.num_conversions () == 0)
    error ("textscan: No conversions specified");

  // Read the data.  This is the main loop.
  if (! err)
    {
      for (/* row set ~30 lines above */; row < ntimes || ntimes == -1; row++)
        {
          if (row == 0 || row >= size)
            {
              size += size+1;
              for (std::list<octave_value>::iterator col = out.begin ();
                   col != out.end (); col++)
                *col = (*col).resize (dim_vector (size, 1), 0);
            }

          row_idx(0) = row;
          err = read_format_once (is, fmt_list, out, row_idx, done_after);

          if (err > 0 || ! is || (lines >= ntimes && ntimes > -1))
            break;
        }
    }

  if ((err & 4) && ! return_on_error)
    error ("textscan: Read error in field %d of row %d",
           done_after + 1, row + 1);

  // If file does not end in EOL, do not pad columns with NaN.
  bool uneven_columns = false;
  if (err & 4)
    uneven_columns = true;
  else if (isp.eof ())
    {
      isp.clear ();
      isp.seekg (-1, std::ios_base::end);
      int last_char = isp.get ();
      isp.setstate (isp.eofbit);
      uneven_columns = (last_char != eol1 && last_char != eol2);
    }

  // convert return value to Cell array
  Array<octave_idx_type> ra_idx (dim_vector (1,2));

  // (err & 1) means "error, and no columns read this row
  // FIXME -- This may redundant now that done_after=0 says the same
  if (err & 1)
    done_after = out.size () + 1;

  int valid_rows = (row == ntimes) ? ntimes : ((err & 1) ? row : row+1);
  dim_vector dv (valid_rows, 1);

  ra_idx(0) = 0;
  int i = 0;
  if (! collect_output)
    {
      retval = Cell (dim_vector (1, out.size ()));
      for (std::list<octave_value>::iterator col = out.begin ();
           col != out.end (); col++, i++)
        {
          // trim last columns if that was requested
          if (i == done_after && uneven_columns)
            dv = dim_vector (std::max (valid_rows - 1, 0), 1);

          ra_idx(1) = i;
          retval = do_cat_op (retval, octave_value (Cell (col->resize (dv,0))),
                              ra_idx);
        }
    }
  else  // group adjacent cells of the same type into a single cell
    {
      octave_value cur;                // current cell, accumulating columns
      octave_idx_type group_size = 0;  // columns in this cell
      int prev_type = -1;

      conv = 0;
      retval = Cell ();
      for (std::list<octave_value>::iterator col = out.begin ();
           col != out.end (); col++)
        {
          if (! merge_with_prev [conv++])  // including first time
            {
              if (prev_type != -1)
                {
                  ra_idx(1) = i++;
                  retval = do_cat_op (retval, octave_value (Cell (cur)),
                                      ra_idx);
                }
              cur = octave_value (col->resize (dv,0));
              group_size = 1;
              prev_type = col->type_id ();
            }
          else
            {
              ra_idx(1) = group_size++;
              cur = do_cat_op (cur, octave_value (col->resize (dv,0)),
                               ra_idx);
            }
        }
      ra_idx(1) = i;
      retval = do_cat_op (retval, octave_value (Cell (cur)), ra_idx);
    }

  return retval;
}

// Calculate x^n.  Used for ...e+nn so that, for example, 1e2 is
// exactly 100 and 5e-1 is 1/2

static double
pown (double x, unsigned int n)
{
  double retval = 1;

  for (unsigned int d = n; d; d >>= 1)
    {
      if (d & 1)
        retval *= x;
      x *= x;
    }

  return retval;
}

// Read a double considering the "precision" field of FMT and the
// EXP_CHARS option of OPTIONS.

double
textscan::read_double (delimited_stream& is,
                       const textscan_format_elt& fmt) const
{
  int sign = 1;
  unsigned int width_left = fmt.width;
  double retval = 0;
  bool valid = false;         // syntactically correct double?

  int ch = is.peek ();

  if (ch == '+')
    {
      is.get ();
      ch = is.peek ();
      if (width_left)
        width_left--;
    }
  else if (ch == '-')
    {
      sign = -1;
      is.get ();
      ch = is.peek ();
      if (width_left)
        width_left--;
    }

  // Read integer part
  if (ch != '.')
    {
      if (ch >= '0' && ch <= '9')       // valid if at least one digit
        valid = true;
      while (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9')
        retval = retval * 10 + (ch - '0');
      width_left++;
    }

  // Read fractional part, up to specified precision
  if (ch == '.' && width_left)
    {
      double multiplier = 1;
      int precision = fmt.prec;
      int i;

      if (width_left)
        width_left--;                  // Consider width of '.'

      if (precision == -1)
        precision = 1<<30;           // FIXME Should be MAXINT

      if (! valid)                    // if there was nothing before '.'...
        is.get ();                   // ...ch was a "peek", not "get".

      for (i = 0; i < precision; i++)
        {
          if (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9')
            retval += (ch - '0') * (multiplier *= 0.1);
          else
            {
              width_left++;
              break;
            }
        }

      // round up if we truncated and the next digit is >= 5
      if ((i == precision || ! width_left) && (ch = is.get ()) >= '5'
          && ch <= '9')
        retval += multiplier;

      if (i > 0)
        valid = true;           // valid if at least one digit after '.'

      // skip remainder after '.', to field width, to look for exponent
      if (i == precision)
        while (width_left-- && is && (ch = is.get ()) >= '0' && ch <= '9')
          ;  // discard

      width_left++;
    }

  // look for exponent part in, e.g.,  6.023E+23
  bool used_exp = false;
  if (valid && width_left > 1 && exp_chars.find (ch) != std::string::npos)
    {
      int ch1 = is.peek ();
      if (ch1 == '-' || ch1 == '+' || (ch1 >= '0' && ch1 <= '9'))
        {          // if 1.0e+$ or some such, this will set failbit, as we want
          width_left--;                         // count "E"
          int exp = 0;
          int exp_sign = 1;
          if (ch1 == '+')
            {
              if (width_left)
                width_left--;
              is.get ();
            }
          else if (ch1 == '-')
            {
              exp_sign = -1;
              is.get ();
              if (width_left)
                width_left--;
            }
          valid = false;
          while (width_left-- && is && (ch = is.get ()) >= '0' && ch1 <= '9')
            {
              exp = exp*10 + ch - '0';
              valid = true;
            }
          width_left++;
          if (ch != std::istream::traits_type::eof () && width_left)
            is.putback (ch);

          double multiplier = pown (10, exp);
          if (exp_sign > 0)
            retval *= multiplier;
          else
            retval /= multiplier;

          used_exp = true;
        }
    }
  is.clear ();
  if (! used_exp && ch != std::istream::traits_type::eof () && width_left)
    is.putback (ch);

  // Check for +/- inf and NaN
  if (! valid && width_left >= 3)
    {
      int i = lookahead (is, inf_nan, 3, false);   // false -> case insensitive
      if (i == 0)
        {
          retval = octave_Inf;
          valid = true;
        }
      else if (i == 1)
        {
          retval = octave_NaN;
          valid = true;
        }
    }

  // Check for +/- inf and NaN
  if (! valid && width_left >= 3)
    {
      int i = lookahead (is, inf_nan, 3, false);   // false -> case insensitive
      if (i == 0)
        {
          retval = octave_Inf;
          valid = true;
        }
      else if (i == 1)
        {
          retval = octave_NaN;
          valid = true;
        }
    }

  if (! valid)
    is.setstate (std::ios::failbit);
  else
    is.setstate (is.rdstate () & ~std::ios::failbit);

  return retval * sign;
}

// Read a single number: real, complex, inf, NaN, possibly with limited
// precision.  Calls to this should be preceded by skip_whitespace.
// Calling that inside scan_complex would violate its const declaration.

void
textscan::scan_complex (delimited_stream& is, const textscan_format_elt& fmt,
                        Complex& val) const
{
  double im = 0;
  double re = 0;
  bool as_empty = false;   // did we fail but match a "treat_as_empty" string?
  bool inf = false;

  int ch = is.peek ();
  if (ch == '+' || ch == '-')   // check for [+-][ij] with no coefficients
    {
      ch = is.get ();
      int ch2 = is.peek ();
      if (ch2 == 'i' || ch2 == 'j')
        {
          double value = 1;
          is.get ();
          // Check not -inf
          if (is.peek () == 'n')
            {
              char *pos = is.tellg ();
              std::ios::iostate state = is.rdstate ();

              is.get ();
              ch2 = is.get ();
              if (ch2 == 'f')
                {
                  inf = true;
                  re = (ch == '+') ? octave_Inf : -octave_Inf;
                  value = 0;
                }
              else
                {
                  is.clear (state);
                  is.seekg (pos);   // reset to position before look-ahead
                }
            }

          im = (ch == '+') ? value : -value;
        }
      else
        is.putback (ch);
    }

  if (! im && ! inf)        // if not [+-][ij] or [+-]inf, read real normally
    {
      char *pos = is.tellg ();
      std::ios::iostate state = is.rdstate ();
      //re = octave_read_value<double> (is);
      re = read_double (is, fmt);

      // check for "treat as empty" string
      if (treat_as_empty.numel ()
          && (is.fail () || octave_is_NaN_or_NA (Complex (re))
              || re == octave_Inf))
        {

          for (int i = 0; i < treat_as_empty.numel (); i++)
            {
              if (ch == treat_as_empty (i).string_value ()[0])
                {
                  as_empty = true;   // first char matches, so read the lot
                  break;
                }
            }
          if (as_empty)              // if first char matched...
            {
              as_empty = false;      // ...look for the whole string

              is.clear (state);      // treat_as_empty "-" causes partial read
              is.seekg (pos);        // reset to position before failed read

              // treat_as_empty strings may be different sizes.
              // Read ahead longest, put it all back, then re-read the string
              // that matches.
              std::string look_buf (treat_as_empty_len, '\0');
              char *look = is.read (&look_buf[0], look_buf.size (), pos);

              is.clear (state);
              is.seekg (pos);        // reset to position before look-ahead
                                     // FIXME -- is.read could invalidate pos

              for (int i = 0; i < treat_as_empty.numel (); i++)
                {
                  std::string s = treat_as_empty (i).string_value ();
                  if (! strncmp (s.c_str (), look, s.size ()))
                    {
                      as_empty = true;
                                     // read just the right amount
                      is.read (&look_buf[0], s.size (), pos);
                      break;
                    }
                }
            }
        }

      if (! is.eof () && ! as_empty)
        {
          state = is.rdstate ();        // before tellg, since that fails at EOF
          pos = is.tellg ();
          ch = is.peek ();   // ch == EOF if read failed; no need to chk fail
          if (ch == 'i' || ch == 'j')           // pure imaginary
            {
              is.get ();
              im = re;
              re = 0;
            }
          else if (ch == '+' || ch == '-')      // see if it is real+imag[ij]
            {
              // save stream state in case we have to restore it
              pos   = is.tellg ();
              state = is.rdstate ();

              //im = octave_read_value<double> (is);
              im = read_double (is, fmt);
              if (is.fail ())
                im = 1;

              if (is.peek () == 'i' || is.peek () == 'j')
                is.get ();
              else
                {
                  im = 0;           // no valid imaginary part.  Restore state
                  is.clear (state); // eof shouldn't cause fail.
                  is.seekg (pos);
                }
            }
          else if (is.eof ())       // we've read enough to be a "valid" read
            is.clear (state);       // failed peek shouldn't cause fail
        }
    }
  if (as_empty)
    val = empty_value.scalar_value ();
  else
    val = Complex (re, im);
}

// Return in VAL the run of characters from IS NOT contained in PATTERN.

int
textscan::scan_caret (delimited_stream& is, const std::string& pattern,
                      std::string& val) const
{
  int c1 = std::istream::traits_type::eof ();
  std::ostringstream obuf;              // Is this optimised for growing?

  while (is && ((c1 = (is && ! is.eof ())
                 ? is.get_undelim ()
                 : std::istream::traits_type::eof ())
                != std::istream::traits_type::eof ())
         && pattern.find (c1) == std::string::npos)
    obuf << static_cast<char> (c1);

  val = obuf.str ();

  if (c1 != std::istream::traits_type::eof ())
    is.putback (c1);

  return c1;
}

// Read until one of the strings in DELIMITERS is found.  For
// efficiency, ENDS is a list of the last character of each delimiter.

std::string
textscan::read_until (delimited_stream& is, const Cell& delimiters,
                     const std::string& ends) const
{
  std::string retval ("");
  bool done = false;
  do
    {                               // find sequence ending with an ending char
      std::string next;
      scan_caret (is, ends.c_str (), next);
      retval = retval + next;   // FIXME -- could use repeated doubling of size

      int last = (! is.eof ()
                  ? is.get_undelim () : std::istream::traits_type::eof ());

      if (last != std::istream::traits_type::eof ())
        {
          retval = retval + static_cast<char> (last);
          for (int i = 0; i < delimiters.numel (); i++)
            {
              std::string delim = delimiters(i).string_value ();
              size_t start = (retval.length () > delim.length ()
                              ? retval.length () - delim.length ()
                              : 0);
              std::string may_match = retval.substr (start);
              if (may_match == delim)
                {
                  done = true;
                  retval = retval.substr (0, start);
                  break;
                }
            }
        }
    }
  while (! done && is && ! is.eof ());

  return retval;
}


// Read stream until either fmt.width chars have been read, or
// options.delimiter has been found.  Does *not* rely on fmt being 's'.
// Used by formats like %6f to limit to 6.

void
textscan::scan_string (delimited_stream& is, const textscan_format_elt& fmt,
                       std::string& val) const
{
  if (delim_list.numel () == 0)
    {
      unsigned int i = 0;
      unsigned int width = fmt.width;

      for (i = 0; i < width; i++)
        {
          if (i+1 > val.length ())
            val = val + val + ' ';      // grow even if empty
          int ch = is.get ();
          if (is_delim (ch) || ch == std::istream::traits_type::eof ())
            {
              is.putback (ch);
              break;
            }
          else
            val[i] = ch;
        }
      val = val.substr (0, i);          // trim pre-allocation
    }
  else  // Cell array of multi-character delimiters
    {
      std::string ends ("");
      for (int i = 0; i < delim_list.numel (); i++)
        {
          std::string tmp = delim_list(i).string_value ();
          ends += tmp.substr (tmp.length () - 1);
        }
      val = textscan::read_until (is, delim_list, ends);
    }
}

// Return in VAL the run of characters from IS contained in PATTERN.

int
textscan::scan_bracket (delimited_stream& is, const std::string& pattern,
                        std::string& val) const
{
  int c1 = std::istream::traits_type::eof ();
  std::ostringstream obuf;              // Is this optimised for growing?

  while (is && pattern.find (c1 = is.get_undelim ()) != std::string::npos)
    obuf << static_cast<char> (c1);

  val = obuf.str ();
  if (c1 != std::istream::traits_type::eof ())
    is.putback (c1);
  return c1;
}

// Return in VAL a string, either delimited by whitespace/delimiters, or
// enclosed in a pair of double quotes ("...").  Enclosing quotes are
// removed.  A consecutive pair "" is inserted into VAL as a single ".

void
textscan::scan_qstring (delimited_stream& is, const textscan_format_elt& fmt,
                        std::string& val)
{
  skip_whitespace (is);

  if (is.peek () != '\"')
    scan_string (is, fmt, val);
  else
    {
      is.get ();
      scan_caret (is, "\"", val);       // read everything until "
      is.get ();                        // swallow "

      while (is && is.peek () == '\"')  // if double ", insert one in stream,
        {                               // and keep looking for single "
          is.get ();
          std::string val1;
          scan_caret (is, "\"", val1);
          val = val + "\"" + val1;
          is.get_undelim ();
        }
    }
}

// Read from IS into VAL a string of the next fmt.width characters,
// including any whitespace or delimiters.

void
textscan::scan_cstring (delimited_stream& is, const textscan_format_elt& fmt,
                        std::string& val) const
{
  val.resize (fmt.width);

  for (unsigned int i = 0; is && i < fmt.width; i++)
    {
      int ch = is.get_undelim ();
      if (ch != std::istream::traits_type::eof ())
        val[i] = ch;
      else
        {
          val.resize (i);
          break;
        }
    }
}


//  Read a single '%...' conversion and place it in position ROW of OV.

void
textscan::scan_one (delimited_stream& is, const textscan_format_elt& fmt,
                    octave_value& ov, Array<octave_idx_type> row)
{
  skip_whitespace (is);

  is.clear ();

  octave_value val;
  if (fmt.numeric)
    {
      if (fmt.type == 'f' || fmt.type == 'n')
        {
          Complex v;
          skip_whitespace (is);
          scan_complex (is, fmt, v);

          if (! fmt.discard && ! is.fail ())
            {
              if (fmt.bitwidth == 64)
                {
                  if (ov.is_real_type () && v.imag () == 0)
                    ov.internal_rep ()->fast_elem_insert (row(0), v.real ());
                  else
                    {
                      if (ov.is_real_type ())  // cat does type conversion
                        ov = do_cat_op (ov, octave_value (v), row);
                      else
                        ov.internal_rep ()->fast_elem_insert (row(0), v);
                    }
                }
              else
                {
                  if (ov.is_real_type () && v.imag () == 0)
                    ov.internal_rep ()->fast_elem_insert (row(0),
                                                         float (v.real ()));
                  else
                    {
                      if (ov.is_real_type ())  // cat does type conversion
                        ov = do_cat_op (ov, octave_value (v), row);
                      else
                        ov.internal_rep ()->fast_elem_insert (row(0),
                                                             FloatComplex (v));
                    }
                }
            }
        }
      else
        {
          double v;    // Matlab docs say 1e30 etc should be valid for %d and
                       // 1000 as a %d8 should be 127, so read as double.
                       // Some loss of precision for d64 and u64.
          skip_whitespace (is);
          v = read_double (is, fmt);
          if (! fmt.discard && ! is.fail ())
            switch (fmt.bitwidth)
              {
              case 64:
                switch (fmt.type)
                  {
                  case 'd':
                    {
                      octave_int64 vv = v;
                      ov.internal_rep ()->fast_elem_insert (row(0), vv);
                    }
                    break;

                  case 'u':
                    {
                      octave_uint64 vv = v;
                      ov.internal_rep ()->fast_elem_insert (row(0), vv);
                    }
                    break;
                  }
                break;

              case 32:
                switch (fmt.type)
                  {
                  case 'd':
                    {
                      octave_int32 vv = v;
                      ov.internal_rep ()->fast_elem_insert (row(0), vv);
                    }
                    break;

                  case 'u':
                    {
                      octave_uint32 vv = v;
                      ov.internal_rep ()->fast_elem_insert (row(0), vv);
                    }
                    break;
                  }
                break;

              case 16:
                if (fmt.type == 'd')
                  {
                    octave_int16 vv = v;
                    ov.internal_rep ()->fast_elem_insert (row(0), vv);
                  }
                else
                  {
                    octave_uint16 vv = v;
                    ov.internal_rep ()->fast_elem_insert (row(0), vv);
                  }
                break;

              case 8:
                if (fmt.type == 'd')
                  {
                    octave_int8 vv = v;
                    ov.internal_rep ()->fast_elem_insert (row(0), vv);
                  }
                else
                  {
                    octave_uint8 vv = v;
                    ov.internal_rep ()->fast_elem_insert (row(0), vv);
                  }
                break;
              }
        }

      if (is.fail ())
        {
          if (! fmt.discard)
            ov = do_cat_op (ov, empty_value, row);

          // If we are continuing after errors, skip over this field
          if (return_on_error == 2)
            {
              std::ios::iostate state = is.rdstate ();
              is.clear ();          // clear to allow read pointer to advance

              std::string dummy;
              scan_string (is, fmt, dummy);

              is.setstate (state);
            }
        }

    }
  else
    {
      std::string vv ("        ");      // initial buffer.  Grows as needed
      switch (fmt.type)
        {
        case 's':
          scan_string (is, fmt, vv);
          break;

        case 'q':
          scan_qstring (is, fmt, vv);
          break;

        case 'c':
          scan_cstring (is, fmt, vv);
          break;

        case '[':
          scan_bracket (is, fmt.char_class.c_str (), vv);
          break;

        case '^':
          scan_caret   (is, fmt.char_class.c_str (), vv);
          break;
        }

      if (! fmt.discard)
        ov.internal_rep ()->fast_elem_insert (row (0),
                                              Cell (octave_value (vv)));

      // FIXME -- why does failbit get set at EOF, instead of eofbit?
      if (! vv.empty ())
        is.clear (is.rdstate () & ~std::ios_base::failbit);
    }

  is.field_done ();
}

// Read data corresponding to the entire format string once, placing the
// values in row ROW of retval.

int
textscan::read_format_once (delimited_stream& is,
                            textscan_format_list& fmt_list,
                            std::list<octave_value>& retval,
                            Array<octave_idx_type> row, int& done_after)
{
  const textscan_format_elt *elem = fmt_list.first ();
  std::list<octave_value>::iterator out = retval.begin ();
  bool no_conversions = true;
  bool done = false;
  bool conversion_failed = false;       // Record for ReturnOnError

  octave_quit ();

  for (size_t i = 0; i < fmt_list.numel (); i++)
    {
      bool this_conversion_failed = false;

      // Clear fail of previous numeric conversions.
      is.clear ();

      switch (elem->type)
        {
        case 'C':
        case 'D':
          std::cerr << "textscan: Conversion %" << elem->type
                    << " not yet implemented\n";
          break;

        case 'u':
        case 'd':
        case 'f':
        case 'n':
        case 's':
        case '[':
        case '^':
        case 'q':
        case 'c':
          scan_one (is, *elem, *out, row);
          break;

        case textscan_format_elt::literal_conversion :
          match_literal (is, *elem);
          break;

        default:
          error ("Unknown format element '%c'", elem->type);
        }

      if (! is.fail ())
        {
          if (! elem->discard)
            no_conversions = false;
        }
      else
        {
          if (return_on_error < 2)
            this_conversion_failed = true;

          is.clear (is.rdstate () & ~std::ios::failbit);
        }

      if (! elem->discard)
        out++;

      elem = fmt_list.next ();
      char *pos = is.tellg ();

      // FIXME -- these conversions "ignore delimiters".  Should they include
      // delimiters at the start of the conversion, or can those be skipped?
      if (elem->type != textscan_format_elt::literal_conversion
          // && elem->type != '[' && elem->type != '^' && elem->type != 'c'
         )
        skip_delim (is);

      if (this_conversion_failed)
        {
          if (is.tellg () == pos && ! conversion_failed)
            {                 // done_after = first failure
              done_after = i; // note fail, but parse others to get empty_val
              conversion_failed = true;
            }
          else
            this_conversion_failed = false;
        }

      if (is.eof ())
        {
          if (! done)
            done_after = i+1;

          // note EOF, but process others to get empty_val.
          done = true;
        }
    }

  if (done)
    is.setstate (std::ios::eofbit);

  // Returning -3 means "error, and no columns read this row".
  if (is.eof ())
    return (2 + no_conversions);

  if (conversion_failed)
    return (4 + no_conversions);

  return 0;
}

void
textscan::parse_options (const octave_value_list& args,
                         textscan_format_list& fmt_list)
{
  int last = args.length ();
  int n = last;

  if (n & 1)
    error ("textscan: %d parameters given, but only %d values", n-n/2, n/2);

  delim_len = 1;
  bool have_delims = false;
  for (int i = 0; i < last; i += 2)
    {
      if (! args(i).is_string ())
        error ("textscan: Invalid paramter type <%s> for parameter %d",
               args(i).type_name ().c_str (), i/2 + 1);

      std::string param = args(i).string_value ();
      std::transform (param.begin (), param.end (),
                      param.begin (), ::tolower);
      if (param == "delimiter")
        {
          bool invalid = true;
          if (args(i+1).is_string ())
            {
              invalid = false;
              have_delims = true;
              delims = args(i+1).string_value ();
            }
          else if (args(i+1).is_cell ())
            {
              invalid = false;
              delim_list = args(i+1).cell_value ();
              delim_table = " "; // non-empty, to flag non-default delim

              // Check that all elements are strings, and find max length
              for (int j = 0; j < delim_list.numel (); j++)
                {
                  if (! delim_list(j).is_string ())
                    invalid = true;
                  else
                    {
                      octave_idx_type len = delim_list(j).string_value ()
                                                         .length ();
                      delim_len = std::max (static_cast<int> (len), delim_len);
                    }
                }
            }
          if (invalid)
            error ("textscan:  Delimiters must be either a string or "
                   "cell array of strings");
        }
      else if (param == "commentstyle")
        {
          if (args(i+1).is_string ())
            {   // check here for names like "C++", "C", "shell", ...?
              comment_style = Cell (args(i+1));
            }
          else if (args(i+1).is_cell ())
            {
              comment_style = args(i+1).cell_value ();
              int len = comment_style.numel ();
              if ((len >= 1 && ! comment_style (0).is_string ())
                  || (len >= 2 && ! comment_style (1).is_string ())
                  || (len >= 3))
                error ("textscan: CommentStyle must be either a string or "
                       "cell array of one or two strings");
            }
          else
            error ("textscan:  CommentStyle must be either a string"
                   " or cell array of one or two strings, not <%s>",
                   args(i+1).class_name ().c_str ());

          // How far ahead do we need to look to detect an open comment
          // and which character do we look for?
          if (comment_style.numel () >= 1)
            {
              comment_len  = comment_style (0).string_value ().size ();
              comment_char = comment_style (0).string_value ()[0];
            }
        }
      else if (param == "treatasempty")
        {
          bool invalid = false;
          if (args(i+1).is_string ())
            {
              treat_as_empty = Cell (args(i+1));
              treat_as_empty_len = args(i+1).string_value ().size ();
            }
          else if (args(i+1).is_cell ())
            {
              treat_as_empty = args(i+1).cell_value ();
              for (int j = 0; j < treat_as_empty.numel (); j++)
                if (! treat_as_empty (j).is_string ())
                  invalid = true;
                else
                  {
                    int k = treat_as_empty (j).string_value ().size ();
                    if (k > treat_as_empty_len)
                      treat_as_empty_len = k;
                  }
            }
          if (invalid)
            error ("textscan:  TreatAsEmpty must be either a string or "
                   "cell array of one or two strings");

          // FIXME Ensure none is a prefix of a later one. Sort by length?
        }
      else if (param == "collectoutput")
        {
          if (args(i+1).is_numeric_type ())
            collect_output = args(i+1).bool_value ();
          else
            error ("textscan:  CollectOutput must be logical or numeric");
        }
      else if (param == "emptyvalue")
        {
          if (args(i+1).is_numeric_type ())
            empty_value = args(i+1).scalar_value ();
          else
            error ("textscan: EmptyValue must be numeric, not <%s>",
                   args(i+1).class_name ().c_str ());
        }
      else if (param == "headerlines")
        {
          if (args(i+1).is_numeric_type ())
            header_lines = args(i+1).scalar_value ();
          else
            error ("textscan: HeaderLines must be numeric");
        }
      else if (param == "bufsize")
        {
          if (args(i+1).is_numeric_type ())
            buffer_size = args(i+1).scalar_value ();
          else
            error ("textscan: BufSize must be numeric");
        }
      else if (param == "multipledelimsasone")
        {
          if (args(i+1).is_numeric_type ())
            {
              if (args(i+1).bool_value ())
                multiple_delims_as_one = true;
            }
          else
            error ("textscan: MultipleDimsAsOne must be logical or numeric");
        }
      else if (param == "returnonerror")
        {
          if (args(i+1).is_numeric_type ())
            return_on_error = args(i+1).bool_value ();
          else if (args(i+1).is_string ()
                   && args(i+1).string_value () == "continue")
            return_on_error = 2;
          else
            error ("textscan: ReturnOnError must be logical or "
                   "numeric, or 'continue'");
        }
      else if (param == "whitespace")
        {
          if (args(i+1).is_string ())
            whitespace = args(i+1).string_value ();
          else
            error ("textscan: Whitespace must be a character string");
        }
      else if (param == "expchars")
        {
          if (args(i+1).is_string ())
            {
              exp_chars = args(i+1).string_value ();
              default_exp = false;
            }
          else
            error ("textscan: ExpChars must be a character string");
        }
      else if (param == "endofline")
        {
          bool valid = true;
          if (args(i+1).is_string ())
            {
              std::string s = args(i+1).string_value ();
              if (args(i+1).is_sq_string ())
                s = do_string_escapes (s);
              int l = s.length ();
              if (l == 0)
                eol1 = eol2 = -2;
              else if (l == 1)
                eol1 = eol2 = s.c_str ()[0];
              else if (l == 2)
                {
                  eol1 = s.c_str ()[0];
                  eol2 = s.c_str ()[1];
                  if (eol1 != '\r' || eol2 != '\n')    // Why limit it?
                    valid = false;
                }
              else
                valid = false;
            }
          else
            valid = false;
          if (! valid)
            error ("textscan: EndOfLine must be at most one character "
                   "or '\\r\\n'");
        }
      else
        error ("textscan: Unrecognised option '%s'", param.c_str ());
    }

  whitespace_table = std::string (256, '\0');
  for (unsigned int i = 0; i < whitespace.length (); i++)
    whitespace_table[whitespace[i]] = '1';

  // For Matlab compatibility, add 0x20 to whitespace, unless
  // whitespace is explicitly ignored.
  if (! (whitespace.empty () && fmt_list.has_string))
    whitespace_table[' '] = '1';

  // Create look-up table of delimiters, based on 'delimiter'
  delim_table = std::string (256, '\0');
  if (eol1 >= 0 && eol1 < 256)
    delim_table[eol1] = '1';        // EOL is always a delimiter
  if (eol2 >= 0 && eol2 < 256)
    delim_table[eol2] = '1';        // EOL is always a delimiter
  if (! have_delims)
    for (unsigned int i = 0; i < 256; i++)
      {
        if (isspace (i))
          delim_table[i] = '1';
      }
  else
    for (unsigned int i = 0; i < delims.length (); i++)
      delim_table[delims[i]] = '1';
}

// Skip comments, and characters specified by the "Whitespace" option.
// If EOLstop == true, don't skip end of line.

int
textscan::skip_whitespace (delimited_stream& is, bool EOLstop)
{
  int c1 = std::istream::traits_type::eof ();
  bool found_comment = false;

  do
    {
      found_comment = false;
      int prev = -1;
      while (is && (c1 = is.get_undelim ()) != std::istream::traits_type::eof ()
             && ( ( (c1 == eol1 || c1 == eol2) && ++lines && ! EOLstop)
                  || isspace (c1)))
        {
          if (prev == eol1 && eol1 != eol2 && c1 == eol2)
            lines--;
          prev = c1;
        }

      if (c1 == comment_char)           // see if we match an open comment
        {
          // save stream state in case we have to restore it
          char *pos   = is.tellg ();
          std::ios::iostate state = is.rdstate ();

          std::string tmp (comment_len, '\0');
          char *look = is.read (&tmp[0], comment_len-1, pos); // already read first char
          if (is && ! strncmp (comment_style(0).string_value ().substr (1)
                               .c_str (), look, comment_len-1))
            {
              found_comment = true;

              std::string dummy;
              if (comment_style.numel () == 1)  // skip to end of line
                {
                  std::string eol (3, '\0');
                  eol[0] = eol1;
                  eol[1] = eol2;

                  scan_caret (is, eol, dummy);
                  c1 = is.get_undelim ();
                  if (c1 == eol1 && eol1 != eol2 && is.peek_undelim () == eol2)
                    is.get_undelim ();
                  lines++;
                }
              else      // matching pair
                {
                  std::string end_c = comment_style(1).string_value ();
                  // last char of end-comment sequence
                  std::string last = end_c.substr (end_c.size () - 1);
                  std::string may_match ("");
                  do
                    {           // find sequence ending with last char
                      scan_caret (is, last, dummy);
                      is.get_undelim ();        // (read LAST itself)

                      may_match = may_match + dummy + last;
                      if (may_match.length () > end_c.length ())
                        {
                          size_t start = may_match.length () - end_c.length ();
                          may_match = may_match.substr (start);
                        }
                    }
                  while (may_match != end_c && is && ! is.eof ());
                }
            }
          else  // wasn't really a comment; restore state
            {
              is.clear (state);
              is.seekg (pos);
            }
        }
    }
  while (found_comment);

  if (c1 != std::istream::traits_type::eof ())
    is.putback (c1);
  return c1;
}

// See if the next few characters match one of the strings in target.
// For efficiency, MAX_LEN is the cached longest length of any target.
// Return -1 if none is found, or the index of the match.

int
textscan::lookahead (delimited_stream& is, const Cell& targets, int max_len,
                     bool case_sensitive) const
{
  // target strings may be different sizes.
  // Read ahead longest, put it all back, then re-read the string
  // that matches.

  char *pos = is.tellg ();

  std::string tmp (max_len, '\0');
  char *look = is.read (&tmp[0], tmp.size (), pos);

  is.clear ();
  is.seekg (pos);              // reset to position before look-ahead
                               // FIXME  pos may be corrupted by is.read

  int i;
  int (*compare)(const char *, const char *, size_t);
  compare = case_sensitive ? strncmp : strncasecmp;

  for (i = 0; i < targets.numel (); i++)
    {
      std::string s = targets (i).string_value ();
      if (! (*compare) (s.c_str (), look, s.size ()))
        {
          is.read (&tmp[0], s.size (), pos); // read just the right amount
          break;
        }
    }

  if (i == targets.numel ())
    i = -1;

  return i;
}

// Skip delimiters -- multiple if MultipleDelimsAsOne specified.
int
textscan::skip_delim (delimited_stream& is)
{
  int c1 = skip_whitespace (is, true);  // 'true': stop once EOL is read
  if (delim_list.numel () == 0)         // single character delimiter
    {
      if (is_delim (c1) || c1 == eol1 || c1 == eol2)
        {
          is.get ();
          if (c1 == eol1 && is.peek_undelim () == eol2)
            is.get_undelim ();          // if \r\n, skip the \n too.

          if (multiple_delims_as_one)
            {
              int prev = -1;
              // skip multiple delims.
              // Increment lines for each end-of-line seen; for \r\n, decrement
              while (is && ((c1 = is.get_undelim ())
                            != std::istream::traits_type::eof ())
                     && (((c1 == eol1 || c1 == eol2) && ++lines)
                         || isspace (c1) || is_delim (c1)))
                {
                  if (prev == eol1 && eol1 != eol2 && c1 == eol2)
                    lines--;
                  prev = c1;
                }
              if (c1 != std::istream::traits_type::eof ())
                is.putback (c1);
            }
        }
    }
  else                                  // multi-character delimiter
    {
      int first_match;

      if (c1 == eol1 || c1 == eol2
          || (-1 != (first_match = lookahead (is, delim_list, delim_len))))
        {
          if (c1 == eol1)
            {
              is.get_undelim ();
              if (is.peek_undelim () == eol2)
                is.get_undelim ();
            }
          else if (c1 == eol2)
            {
              is.get_undelim ();
            }

          if (multiple_delims_as_one)
            {
              int prev = -1;
              // skip multiple delims.
              // Increment lines for each end-of-line seen; for \r\n, decrement
              while (is && ((c1 = skip_whitespace (is, true))
                            != std::istream::traits_type::eof ())
                     && (((c1 == eol1 || c1 == eol2) && ++lines)
                         || -1 != lookahead (is, delim_list, delim_len)))
                {
                  if (prev == eol1 && eol1 != eol2 && c1 == eol2)
                    lines--;
                  prev = c1;
                }
            }
        }
    }

  return c1;
}

// Read in as much of the input as coincides with the literal in the
// format string.  Return "true" if the entire literal is matched, else
// false (and set failbit).

bool
textscan::match_literal (delimited_stream& is, const textscan_format_elt& fmt)
{
     // "false" -> treat EOL as normal space
     // since a delimiter at the start of a line is a mismatch, not empty field
  skip_whitespace (is, false);

  for (unsigned int i = 0; i < fmt.width; i++)
    {
      int ch = is.get_undelim ();
      if (ch != fmt.text[i])
        {
          if (ch != std::istream::traits_type::eof ())
            is.putback (ch);
          is.setstate (std::ios::failbit);
          return false;
        }
    }
  return true;
}

void
octave_base_stream::error (const std::string& msg)
{
  fail = true;
  errmsg = msg;
}

void
octave_base_stream::error (const std::string& who, const std::string& msg)
{
  fail = true;
  errmsg = who + ": " + msg;
}

void
octave_base_stream::clear (void)
{
  fail = false;
  errmsg = "";
}

void
octave_base_stream::clearerr (void)
{
  std::istream *is = input_stream ();
  std::ostream *os = output_stream ();

  if (is)
    is->clear ();

  if (os)
    os->clear ();
}

// Functions that are defined for all input streams (input streams
// are those that define is).

std::string
octave_base_stream::do_gets (octave_idx_type max_len, bool& err,
                             bool strip_newline, const std::string& who)
{
  if (interactive && file_number () == 0)
    ::error ("%s: unable to read from stdin while running interactively",
             who.c_str ());

  std::string retval;

  err = false;

  std::istream *isp = input_stream ();

  if (! isp)
    {
      err = true;
      invalid_operation (who, "reading");
    }
  else
    {
      std::istream& is = *isp;

      std::ostringstream buf;

      int c = 0;
      int char_count = 0;

      if (max_len != 0)
        {
          while (is && (c = is.get ()) != std::istream::traits_type::eof ())
            {
              char_count++;

              // Handle CRLF, CR, or LF as line ending.
              if (c == '\r')
                {
                  if (! strip_newline)
                    buf << static_cast<char> (c);

                  c = is.get ();

                  if (c != std::istream::traits_type::eof ())
                    {
                      if (c == '\n')
                        {
                          char_count++;

                          if (! strip_newline)
                            buf << static_cast<char> (c);
                        }
                      else
                        is.putback (c);
                    }

                  break;
                }
              else if (c == '\n')
                {
                  if (! strip_newline)
                    buf << static_cast<char> (c);

                  break;
                }
              else
                buf << static_cast<char> (c);

              if (max_len > 0 && char_count == max_len)
                break;
            }
        }

      if (! is.eof () && char_count > 0)
        {
          // GAGME.  Matlab seems to check for EOF even if the last character
          // in a file is a newline character.  This is NOT what the
          // corresponding C-library functions do.
          int disgusting_compatibility_hack = is.get ();
          if (! is.eof ())
            is.putback (disgusting_compatibility_hack);
        }

      if (is.good () || (is.eof () && char_count > 0))
        retval = buf.str ();
      else
        {
          err = true;

          if (is.eof () && char_count == 0)
            error (who, "at end of file");
          else
            error (who, "read error");
        }
    }

  return retval;
}

std::string
octave_base_stream::getl (octave_idx_type max_len, bool& err,
                          const std::string& who)
{
  return do_gets (max_len, err, true, who);
}

std::string
octave_base_stream::gets (octave_idx_type max_len, bool& err,
                          const std::string& who)
{
  return do_gets (max_len, err, false, who);
}

off_t
octave_base_stream::skipl (off_t num, bool& err, const std::string& who)
{
  if (interactive && file_number () == 0)
    ::error ("%s: unable to read from stdin while running interactively",
             who.c_str ());

  off_t cnt = -1;

  err = false;

  std::istream *isp = input_stream ();

  if (! isp)
    {
      err = true;
      invalid_operation (who, "reading");
    }
  else
    {
      std::istream& is = *isp;

      int c = 0;
      int lastc = -1;
      cnt = 0;

      while (is && (c = is.get ()) != std::istream::traits_type::eof ())
        {
          // Handle CRLF, CR, or LF as line ending.
          if (c == '\r' || (c == '\n' && lastc != '\r'))
            {
              if (++cnt == num)
                break;
            }

          lastc = c;
        }

      // Maybe eat the following \n if \r was just met.
      if (c == '\r' && is.peek () == '\n')
        is.get ();

      if (is.bad ())
        {
          err = true;
          error (who, "read error");
        }

      if (err)
        cnt = -1;
    }

  return cnt;
}

template <typename T>
std::istream&
octave_scan_1 (std::istream& is, const scanf_format_elt& fmt, T* valptr)
{
  T& ref = *valptr;

  switch (fmt.type)
    {
    case 'o':
      is >> std::oct >> ref >> std::dec;
      break;

    case 'x':
      is >> std::hex >> ref >> std::dec;
      break;

    case 'i':
      {
        int c1 = std::istream::traits_type::eof ();

        while (is && (c1 = is.get ()) != std::istream::traits_type::eof ()
               && isspace (c1))
          ; // skip whitespace

        if (c1 != std::istream::traits_type::eof ())
          {
            if (c1 == '0')
              {
                int c2 = is.peek ();

                if (c2 == 'x' || c2 == 'X')
                  {
                    is.ignore ();
                    if (std::isxdigit (is.peek ()))
                      is >> std::hex >> ref >> std::dec;
                    else
                      ref = 0;
                  }
                else
                  {
                    if (c2 == '0' || c2 == '1' || c2 == '2'
                        || c2 == '3' || c2 == '4' || c2 == '5'
                        || c2 == '6' || c2 == '7')
                      is >> std::oct >> ref >> std::dec;
                    else if (c2 == '8' || c2 == '9')
                    {
                      // FIXME: Would like to set error state on octave stream.
                      // See bug #46493.  But only std::istream is input to fcn
                      // error ("internal failure to match octal format");
                      ref = 0;
                    }
                    else
                      ref = 0;
                  }
              }
            else
              {
                is.putback (c1);

                is >> ref;
              }
          }
      }
      break;

    default:
      is >> ref;
      break;
    }

  return is;
}

template <typename T>
std::istream&
octave_scan (std::istream& is, const scanf_format_elt& fmt, T* valptr)
{
  if (fmt.width)
    {
      // Limit input to fmt.width characters by reading into a
      // temporary stringstream buffer.
      std::string tmp;

      is.width (fmt.width);
      is >> tmp;

      std::istringstream ss (tmp);

      octave_scan_1 (ss, fmt, valptr);
    }
  else
    octave_scan_1 (is, fmt, valptr);

  return is;
}

// Note that this specialization is only used for reading characters, not
// character strings. See BEGIN_S_CONVERSION for details.

template <>
std::istream&
octave_scan<> (std::istream& is, const scanf_format_elt& /* fmt */,
               char* valptr)
{
  return is >> valptr;
}

template <>
std::istream&
octave_scan<> (std::istream& is, const scanf_format_elt& fmt, double* valptr)
{
  double& ref = *valptr;

  switch (fmt.type)
    {
    case 'e':
    case 'f':
    case 'g':
      {
        int c1 = std::istream::traits_type::eof ();

        while (is && (c1 = is.get ()) != std::istream::traits_type::eof ()
               && isspace (c1))
          ; // skip whitespace

        if (c1 != std::istream::traits_type::eof ())
          {
            is.putback (c1);

            ref = octave_read_value<double> (is);
          }
      }
      break;

    default:
      panic_impossible ();
      break;
    }

  return is;
}

template <typename T>
void
do_scanf_conv (std::istream& is, const scanf_format_elt& fmt,
               T valptr, Matrix& mval, double *data, octave_idx_type& idx,
               octave_idx_type& conversion_count, octave_idx_type nr,
               octave_idx_type max_size, bool discard)
{
  octave_scan (is, fmt, valptr);

  if (! is)
    return;

  if (idx == max_size && ! discard)
    {
      max_size *= 2;

      if (nr > 0)
        mval.resize (nr, max_size / nr, 0.0);
      else
        mval.resize (max_size, 1, 0.0);

      data = mval.fortran_vec ();
    }

  if (! discard)
    {
      conversion_count++;
      data[idx++] = *(valptr);
    }
}

template void
do_scanf_conv (std::istream&, const scanf_format_elt&, double*,
               Matrix&, double*, octave_idx_type&, octave_idx_type&,
               octave_idx_type, octave_idx_type, bool);

#define DO_WHITESPACE_CONVERSION() \
  do \
    { \
      int c = std::istream::traits_type::eof (); \
 \
      while (is && (c = is.get ()) != std::istream::traits_type::eof () \
             && isspace (c)) \
        { /* skip whitespace */ } \
 \
      if (c != std::istream::traits_type::eof ()) \
        is.putback (c); \
    } \
  while (0)

#define DO_LITERAL_CONVERSION() \
  do \
    { \
      int c = std::istream::traits_type::eof (); \
 \
      int n = strlen (fmt); \
      int i = 0; \
 \
      while (i < n && is && (c = is.get ()) != std::istream::traits_type::eof ()) \
        { \
          if (c == static_cast<unsigned char> (fmt[i])) \
            { \
              i++; \
              continue; \
            } \
          else \
            { \
              is.putback (c); \
              break; \
            } \
        } \
 \
      if (i != n) \
        is.setstate (std::ios::failbit); \
    } \
  while (0)

#define DO_PCT_CONVERSION() \
  do \
    { \
      int c = is.get (); \
 \
      if (c != std::istream::traits_type::eof ()) \
        { \
          if (c != '%') \
            { \
              is.putback (c); \
              is.setstate (std::ios::failbit); \
            } \
        } \
      else \
        is.setstate (std::ios::failbit); \
    } \
  while (0)

#define BEGIN_C_CONVERSION() \
  is.unsetf (std::ios::skipws); \
 \
  int width = elt->width ? elt->width : 1; \
 \
  std::string tmp (width, '\0'); \
 \
  int c = std::istream::traits_type::eof (); \
  int n = 0; \
 \
  while (is && n < width && (c = is.get ()) != std::istream::traits_type::eof ()) \
    tmp[n++] = static_cast<char> (c); \
 \
  if (n > 0 && c == std::istream::traits_type::eof ()) \
    is.clear (); \
 \
  tmp.resize (n)

// For a '%s' format, skip initial whitespace and then read until the
// next whitespace character or until WIDTH characters have been read.
#define BEGIN_S_CONVERSION() \
  int width = elt->width; \
 \
  std::string tmp; \
 \
  do \
    { \
      if (width) \
        { \
          tmp = std::string (width, '\0'); \
 \
          int c = std::istream::traits_type::eof (); \
 \
          int n = 0; \
 \
          while (is && (c = is.get ()) != std::istream::traits_type::eof ()) \
            { \
              if (! isspace (c)) \
                { \
                  tmp[n++] = static_cast<char> (c); \
                  break; \
                } \
            } \
 \
          while (is && n < width \
                 && (c = is.get ()) != std::istream::traits_type::eof ()) \
            { \
              if (isspace (c)) \
                { \
                  is.putback (c); \
                  break; \
                } \
              else \
                tmp[n++] = static_cast<char> (c); \
            } \
 \
          if (n > 0 && c == std::istream::traits_type::eof ()) \
            is.clear (); \
 \
          tmp.resize (n); \
        } \
      else \
        { \
          is >> std::ws >> tmp; \
        } \
    } \
  while (0)

// This format must match a nonempty sequence of characters.
#define BEGIN_CHAR_CLASS_CONVERSION() \
  int width = elt->width; \
 \
  std::string tmp; \
 \
  do \
    { \
      if (! width) \
        width = std::numeric_limits<int>::max (); \
 \
      std::ostringstream buf; \
 \
      std::string char_class = elt->char_class; \
 \
      int c = std::istream::traits_type::eof (); \
 \
      if (elt->type == '[') \
        { \
          int chars_read = 0; \
          while (is && chars_read++ < width \
                 && (c = is.get ()) != std::istream::traits_type::eof () \
                 && char_class.find (c) != std::string::npos) \
            buf << static_cast<char> (c); \
        } \
      else \
        { \
          int chars_read = 0; \
          while (is && chars_read++ < width \
                 && (c = is.get ()) != std::istream::traits_type::eof () \
                 && char_class.find (c) == std::string::npos) \
            buf << static_cast<char> (c); \
        } \
 \
      if (width == std::numeric_limits<int>::max () \
          && c != std::istream::traits_type::eof ()) \
        is.putback (c); \
 \
      tmp = buf.str (); \
 \
      if (tmp.empty ()) \
        is.setstate (std::ios::failbit); \
      else if (c == std::istream::traits_type::eof ()) \
        is.clear (); \
 \
    } \
  while (0)

#define FINISH_CHARACTER_CONVERSION() \
  do \
    { \
      width = tmp.length (); \
 \
      if (is) \
        { \
          int i = 0; \
 \
          if (! discard) \
            { \
              conversion_count++; \
 \
              while (i < width) \
                { \
                  if (data_index == max_size) \
                    { \
                      max_size *= 2; \
 \
                      if (all_char_conv) \
                        { \
                          if (one_elt_size_spec) \
                            mval.resize (1, max_size, 0.0); \
                          else if (nr > 0) \
                            mval.resize (nr, max_size / nr, 0.0); \
                          else \
                            panic_impossible (); \
                        } \
                      else if (nr > 0) \
                        mval.resize (nr, max_size / nr, 0.0); \
                      else \
                        mval.resize (max_size, 1, 0.0); \
 \
                      data = mval.fortran_vec (); \
                    } \
 \
                  data[data_index++] = tmp[i++]; \
                } \
            } \
        } \
    } \
  while (0)

octave_value
octave_base_stream::do_scanf (scanf_format_list& fmt_list,
                              octave_idx_type nr, octave_idx_type nc,
                              bool one_elt_size_spec,
                              octave_idx_type& conversion_count,
                              const std::string& who)
{
  if (interactive && file_number () == 0)
    ::error ("%s: unable to read from stdin while running interactively",
             who.c_str ());

  octave_value retval = Matrix ();

  conversion_count = 0;

  octave_idx_type nconv = fmt_list.num_conversions ();

  octave_idx_type data_index = 0;

  if (nr == 0 || nc == 0)
    {
      if (one_elt_size_spec)
        nc = 0;

      return Matrix (nr, nc, 0.0);
    }

  std::istream *isp = input_stream ();

  bool all_char_conv = fmt_list.all_character_conversions ();

  Matrix mval;
  double *data = 0;
  octave_idx_type max_size = 0;
  octave_idx_type max_conv = 0;

  octave_idx_type final_nr = 0;
  octave_idx_type final_nc = 0;

  if (all_char_conv)
    {
      // Any of these could be resized later (if we have %s conversions,
      // we may read more than one element for each conversion).
      if (one_elt_size_spec)
        {
          max_size = 512;
          mval.resize (1, max_size, 0.0);

          if (nr > 0)
            max_conv = nr;
        }
      else if (nr > 0)
        {
          if (nc > 0)
            {
              mval.resize (nr, nc, 0.0);
              max_size = max_conv = nr * nc;
            }
          else
            {
              mval.resize (nr, 32, 0.0);
              max_size = nr * 32;
            }
        }
      else
        panic_impossible ();
    }
  else if (nr > 0)
    {
      if (nc > 0)
        {
          // Will not resize later.
          mval.resize (nr, nc, 0.0);
          max_size = nr * nc;
          max_conv = max_size;
        }
      else
        {
          // Maybe resize later.
          mval.resize (nr, 32, 0.0);
          max_size = nr * 32;
        }
    }
  else
    {
      // Maybe resize later.
      mval.resize (32, 1, 0.0);
      max_size = 32;
    }

  data = mval.fortran_vec ();

  if (isp)
    {
      std::istream& is = *isp;

      const scanf_format_elt *elt = fmt_list.first ();

      std::ios::fmtflags flags = is.flags ();

      octave_idx_type trips = 0;

      octave_idx_type num_fmt_elts = fmt_list.length ();

      for (;;)
        {
          octave_quit ();

          if (elt)
            {
              if (elt->type == scanf_format_elt::null
                  || (! (elt->type == scanf_format_elt::whitespace_conversion
                        || elt->type == scanf_format_elt::literal_conversion
                        || elt->type == '%')
                      && max_conv > 0 && conversion_count == max_conv))
                {
                  // We are done, either because we have reached the end of the
                  // format string and are not cycling through the format again
                  // or because we've converted all the values that have been
                  // requested and the next format element is a conversion.
                  // Determine final array size and exit.
                  if (all_char_conv && one_elt_size_spec)
                    {
                      final_nr = 1;
                      final_nc = data_index;
                    }
                  else
                    {
                      final_nr = nr;
                      final_nc = (data_index - 1) / nr + 1;
                    }

                  break;
                }
              else if (data_index == max_size)
                {
                  max_size *= 2;

                  if (all_char_conv)
                    {
                      if (one_elt_size_spec)
                        mval.resize (1, max_size, 0.0);
                      else if (nr > 0)
                        mval.resize (nr, max_size / nr, 0.0);
                      else
                        panic_impossible ();
                    }
                  else if (nr > 0)
                    mval.resize (nr, max_size / nr, 0.0);
                  else
                    mval.resize (max_size, 1, 0.0);

                  data = mval.fortran_vec ();
                }

              const char *fmt = elt->text;

              bool discard = elt->discard;

              switch (elt->type)
                {
                case scanf_format_elt::whitespace_conversion:
                  DO_WHITESPACE_CONVERSION ();
                  break;

                case scanf_format_elt::literal_conversion:
                  DO_LITERAL_CONVERSION ();
                  break;

                case '%':
                  DO_PCT_CONVERSION ();
                  break;

                case 'd': case 'i':
                  {
                    switch (elt->modifier)
                      {
                      case 'h':
                        {
                          short int tmp;
                          do_scanf_conv (is, *elt, &tmp, mval, data,
                                         data_index, conversion_count,
                                         nr, max_size, discard);
                        }
                        break;

                      case 'l':
                        {
                          long int tmp;
                          do_scanf_conv (is, *elt, &tmp, mval, data,
                                         data_index, conversion_count,
                                         nr, max_size, discard);
                        }
                        break;

                      default:
                        {
                          int tmp;
                          do_scanf_conv (is, *elt, &tmp, mval, data,
                                         data_index, conversion_count,
                                         nr, max_size, discard);
                        }
                        break;
                      }
                  }
                  break;

                case 'o': case 'u': case 'x':
                  {
                    switch (elt->modifier)
                      {
                      case 'h':
                        {
                          unsigned short int tmp;
                          do_scanf_conv (is, *elt, &tmp, mval, data,
                                         data_index, conversion_count,
                                         nr, max_size, discard);
                        }
                        break;

                      case 'l':
                        {
                          unsigned long int tmp;
                          do_scanf_conv (is, *elt, &tmp, mval, data,
                                         data_index, conversion_count,
                                         nr, max_size, discard);
                        }
                        break;

                      default:
                        {
                          unsigned int tmp;
                          do_scanf_conv (is, *elt, &tmp, mval, data,
                                         data_index, conversion_count,
                                         nr, max_size, discard);
                        }
                        break;
                      }
                  }
                  break;

                case 'e': case 'f': case 'g':
                  {
                    double tmp;

                    do_scanf_conv (is, *elt, &tmp, mval, data,
                                   data_index, conversion_count,
                                   nr, max_size, discard);
                  }
                  break;

                case 'c':
                  {
                    BEGIN_C_CONVERSION ();

                    FINISH_CHARACTER_CONVERSION ();

                    is.setf (flags);
                  }
                  break;

                case 's':
                  {
                    BEGIN_S_CONVERSION ();

                    FINISH_CHARACTER_CONVERSION ();
                  }
                  break;

                case '[': case '^':
                  {
                    BEGIN_CHAR_CLASS_CONVERSION ();

                    FINISH_CHARACTER_CONVERSION ();
                  }
                  break;

                case 'p':
                  error ("%s: unsupported format specifier", who.c_str ());
                  break;

                default:
                  error ("%s: internal format error", who.c_str ());
                  break;
                }

              if (! ok ())
                {
                  break;
                }
              else if (! is)
                {
                  if (all_char_conv)
                    {
                      if (one_elt_size_spec)
                        {
                          final_nr = 1;
                          final_nc = data_index;
                        }
                      else if (data_index > nr)
                        {
                          final_nr = nr;
                          final_nc = (data_index - 1) / nr + 1;
                        }
                      else
                        {
                          final_nr = data_index;
                          final_nc = 1;
                        }
                    }
                  else if (nr > 0)
                    {
                      if (data_index > nr)
                        {
                          final_nr = nr;
                          final_nc = (data_index - 1) / nr + 1;
                        }
                      else
                        {
                          final_nr = data_index;
                          final_nc = 1;
                        }
                    }
                  else
                    {
                      final_nr = data_index;
                      final_nc = 1;
                    }

                  // If it looks like we have a matching failure, then
                  // reset the failbit in the stream state.
                  if (is.rdstate () & std::ios::failbit)
                    is.clear (is.rdstate () & (~std::ios::failbit));

                  // FIXME: is this the right thing to do?
                  if (interactive && ! forced_interactive && name () == "stdin")
                    {
                      is.clear ();

                      // Skip to end of line.
                      bool err;
                      do_gets (-1, err, false, who);
                    }

                  break;
                }
            }
          else
            {
              error ("%s: internal format error", who.c_str ());
              break;
            }

          if (nconv == 0 && ++trips == num_fmt_elts)
            {
              if (all_char_conv && one_elt_size_spec)
                {
                  final_nr = 1;
                  final_nc = data_index;
                }
              else
                {
                  final_nr = nr;
                  final_nc = (data_index - 1) / nr + 1;
                }

              break;
            }
          else
            {
              // Cycle through the format list more than once if we have some
              // conversions to make and we haven't reached the limit on the
              // number of values to convert (possibly because there is no
              // specified limit).
              elt = fmt_list.next (nconv > 0
                                   && (max_conv == 0
                                       || conversion_count < max_conv));
            }
        }
    }

  if (ok ())
    {
      mval.resize (final_nr, final_nc, 0.0);

      retval = mval;

      if (all_char_conv)
        retval = retval.convert_to_str (false, true);
    }

  return retval;
}

octave_value
octave_base_stream::scanf (const std::string& fmt, const Array<double>& size,
                           octave_idx_type& conversion_count,
                           const std::string& who)
{
  octave_value retval = Matrix ();

  conversion_count = 0;

  std::istream *isp = input_stream ();

  if (! isp)
    invalid_operation (who, "reading");
  else
    {
      scanf_format_list fmt_list (fmt);

      if (fmt_list.num_conversions () == -1)
        ::error ("%s: invalid format specified", who.c_str ());

      octave_idx_type nr = -1;
      octave_idx_type nc = -1;

      bool one_elt_size_spec;

      get_size (size, nr, nc, one_elt_size_spec, who);

      retval = do_scanf (fmt_list, nr, nc, one_elt_size_spec,
                         conversion_count, who);
    }

  return retval;
}

bool
octave_base_stream::do_oscanf (const scanf_format_elt *elt,
                               octave_value& retval, const std::string& who)
{
  std::istream *isp = input_stream ();

  if (! isp)
    return false;

  bool quit = false;

  std::istream& is = *isp;

  std::ios::fmtflags flags = is.flags ();

  if (elt)
    {
      const char *fmt = elt->text;

      bool discard = elt->discard;

      switch (elt->type)
        {
        case scanf_format_elt::whitespace_conversion:
          DO_WHITESPACE_CONVERSION ();
          break;

        case scanf_format_elt::literal_conversion:
          DO_LITERAL_CONVERSION ();
          break;

        case '%':
          {
            DO_PCT_CONVERSION ();

            if (! is)
              quit = true;
          }
          break;

        case 'd': case 'i':
          {
            int tmp;

            if (octave_scan (is, *elt, &tmp))
              {
                if (! discard)
                  retval = tmp;
              }
            else
              quit = true;
          }
          break;

        case 'o': case 'u': case 'x':
          {
            long int tmp;

            if (octave_scan (is, *elt, &tmp))
              {
                if (! discard)
                  retval = tmp;
              }
            else
              quit = true;
          }
          break;

        case 'e': case 'f': case 'g':
          {
            double tmp;

            if (octave_scan (is, *elt, &tmp))
              {
                if (! discard)
                  retval = tmp;
              }
            else
              quit = true;
          }
          break;

        case 'c':
          {
            BEGIN_C_CONVERSION ();

            if (! discard)
              retval = tmp;

            if (! is)
              quit = true;

            is.setf (flags);
          }
          break;

        case 's':
          {
            BEGIN_S_CONVERSION ();

            if (! discard)
              retval = tmp;

            if (! is)
              quit = true;
          }
          break;

        case '[':
        case '^':
          {
            BEGIN_CHAR_CLASS_CONVERSION ();

            if (! discard)
              retval = tmp;

            if (! is)
              quit = true;
          }
          break;

        case 'p':
          error ("%s: unsupported format specifier", who.c_str ());
          break;

        default:
          error ("%s: internal format error", who.c_str ());
          break;
        }
    }

  if (ok () && is.fail ())
    {
      error ("%s: read error", who.c_str ());

      // FIXME: is this the right thing to do?

      if (interactive && ! forced_interactive && name () == "stdin")
        {
          // Skip to end of line.
          bool err;
          do_gets (-1, err, false, who);
        }
    }

  return quit;
}

octave_value_list
octave_base_stream::oscanf (const std::string& fmt, const std::string& who)
{
  octave_value_list retval;

  std::istream *isp = input_stream ();

  if (! isp)
    invalid_operation (who, "reading");
  else
    {
      std::istream& is = *isp;

      scanf_format_list fmt_list (fmt);

      octave_idx_type nconv = fmt_list.num_conversions ();

      if (nconv == -1)
        ::error ("%s: invalid format specified", who.c_str ());

      is.clear ();

      octave_idx_type len = fmt_list.length ();

      retval.resize (nconv+2, Matrix ());

      const scanf_format_elt *elt = fmt_list.first ();

      int num_values = 0;

      bool quit = false;

      for (octave_idx_type i = 0; i < len; i++)
        {
          octave_value tmp;

          quit = do_oscanf (elt, tmp, who);

          if (quit)
            break;
          else
            {
              if (tmp.is_defined ())
                retval(num_values++) = tmp;

              if (! ok ())
                break;

              elt = fmt_list.next (nconv > 0);
            }
        }

      retval(nconv) = num_values;

      int err_num;
      retval(nconv+1) = error (false, err_num);

      if (! quit)
        {
          // Pick up any trailing stuff.
          if (ok () && len > nconv)
            {
              octave_value tmp;

              elt = fmt_list.next ();

              do_oscanf (elt, tmp, who);
            }
        }
    }

  return retval;
}

// Functions that are defined for all output streams
// (output streams are those that define os).

int
octave_base_stream::flush (void)
{
  int retval = -1;

  std::ostream *os = output_stream ();

  if (! os)
    invalid_operation ("fflush", "writing");
  else
    {
      os->flush ();

      if (os->good ())
        retval = 0;
    }

  return retval;
}

class
printf_value_cache
{
public:

  enum state { ok, conversion_error };

  printf_value_cache (const octave_value_list& args, const std::string& who)
    : values (args), val_idx (0), elt_idx (0),
      n_vals (values.length ()), n_elts (0), have_data (false),
      curr_state (ok)
  {
    for (octave_idx_type i = 0; i < values.length (); i++)
      {
        octave_value val = values(i);

        if (val.is_map () || val.is_cell () || val.is_object ())
          err_wrong_type_arg (who, val);
      }
  }

  ~printf_value_cache (void) { }

  // Get the current value as a double and advance the internal pointer.
  octave_value get_next_value (char type = 0);

  // Get the current value as an int and advance the internal pointer.
  int int_value (void);

  operator bool () const { return (curr_state == ok); }

  bool exhausted (void) { return (val_idx >= n_vals); }

private:

  const octave_value_list values;
  int val_idx;
  int elt_idx;
  int n_vals;
  int n_elts;
  bool have_data;
  octave_value curr_val;
  state curr_state;

  // Must create value cache with values!

  printf_value_cache (void);

  // No copying!

  printf_value_cache (const printf_value_cache&);

  printf_value_cache& operator = (const printf_value_cache&);
};

octave_value
printf_value_cache::get_next_value (char type)
{
  octave_value retval;

  if (exhausted ())
    curr_state = conversion_error;

  while (! exhausted ())
    {
      if (! have_data)
        {
          curr_val = values (val_idx);

          elt_idx = 0;
          n_elts = curr_val.numel ();
          have_data = true;
        }

      if (elt_idx < n_elts)
        {
          if (type == 's')
            {
              if (curr_val.is_string ())
                {
                  dim_vector dv (1, curr_val.numel ());
                  octave_value tmp = curr_val.reshape (dv);

                  std::string sval = tmp.string_value ();

                  retval = sval.substr (elt_idx);

                  // We've consumed the rest of the value.
                  elt_idx = n_elts;
                }
              else
                {
                  // Convert to character string while values are
                  // integers in the range [0 : char max]
                  const NDArray val = curr_val.array_value ();

                  octave_idx_type idx = elt_idx;

                  for (; idx < n_elts; idx++)
                    {
                      double dval = val(idx);

                      if (D_NINT (dval) != dval || dval < 0 || dval > 255)
                        break;
                    }

                  octave_idx_type n = idx - elt_idx;

                  if (n > 0)
                    {
                      std::string sval (n, '\0');

                      for (octave_idx_type i = 0; i < n; i++)
                        sval[i] = val(elt_idx++);

                      retval = sval;
                    }
                  else
                    retval = curr_val.fast_elem_extract (elt_idx++);
                }
            }
          else
            {
              retval = curr_val.fast_elem_extract (elt_idx++);

              if (type == 'c' && ! retval.is_string ())
                {
                  double dval = retval.double_value ();

                  if (D_NINT (dval) == dval && dval >= 0 && dval < 256)
                    retval = static_cast<char> (dval);
                }
            }

          if (elt_idx >= n_elts)
            {
              elt_idx = 0;
              val_idx++;
              have_data = false;
            }

          break;
        }
      else
        {
          val_idx++;
          have_data = false;

          if (n_elts == 0)
            {
              if (elt_idx == 0 && (type == 's' || type == 'c'))
                {
                  retval = "";
                  break;
                }

              if (exhausted ())
                curr_state = conversion_error;
            }
        }
    }

  return retval;
}

int
printf_value_cache::int_value (void)
{
  int retval = 0;

  octave_value val = get_next_value ();

  double dval = val.double_value (true);

  if (D_NINT (dval) == dval)
    retval = NINT (dval);
  else
    curr_state = conversion_error;

  return retval;
}

// Ugh again and again.

template <typename T>
int
do_printf_conv (std::ostream& os, const char *fmt, int nsa, int sa_1,
                int sa_2, T arg, const std::string& who)
{
  int retval = 0;

  switch (nsa)
    {
    case 2:
      retval = octave_format (os, fmt, sa_1, sa_2, arg);
      break;

    case 1:
      retval = octave_format (os, fmt, sa_1, arg);
      break;

    case 0:
      retval = octave_format (os, fmt, arg);
      break;

    default:
      ::error ("%s: internal error handling format", who.c_str ());
      break;
    }

  return retval;
}

static size_t
do_printf_string (std::ostream& os, const printf_format_elt *elt,
                  int nsa, int sa_1, int sa_2, const std::string& arg,
                  const std::string& who)
{
  if (nsa > 2)
    ::error ("%s: internal error handling format", who.c_str ());

  std::string flags = elt->flags;

  bool left = flags.find ('-') != std::string::npos;

  size_t len = arg.length ();

  size_t fw = nsa > 0 ? sa_1 : (elt->fw == -1 ? len : elt->fw);
  size_t prec = nsa > 1 ? sa_2 : (elt->prec == -1 ? len : elt->prec);

  os << std::setw (fw)
     << (left ? std::left : std::right)
     << (prec < len ? arg.substr (0, prec) : arg);

  return len > fw ? len : fw;
}

static bool
is_nan_or_inf (const octave_value& val)
{
  octave_value ov_isnan = val.isnan ();
  octave_value ov_isinf = val.isinf ();

  return (ov_isnan.is_true () || ov_isinf.is_true ());
}

static bool
ok_for_signed_int_conv (const octave_value& val)
{
  uint64_t limit = std::numeric_limits<int64_t>::max ();

  if (val.is_string ())
    return true;
  else if (val.is_integer_type ())
    {
      if (val.is_uint64_type ())
        {
          octave_uint64 ival = val.uint64_scalar_value ();

          if (ival.value () <= limit)
            return true;
        }
      else
        return true;
    }
  else
    {
      double dval = val.double_value (true);

      if (dval == xround (dval) && dval <= limit)
        return true;
    }

  return false;
}

static bool
ok_for_unsigned_int_conv (const octave_value& val)
{
  if (val.is_string ())
    return true;
  else if (val.is_integer_type ())
    {
      // Easier than dispatching here...

      octave_value ov_is_ge_zero
        = do_binary_op (octave_value::op_ge, val, octave_value (0.0));

      return ov_is_ge_zero.is_true ();
    }
  else
    {
      double dval = val.double_value (true);

      uint64_t limit = std::numeric_limits<uint64_t>::max ();

      if (dval == xround (dval) && dval >= 0 && dval <= limit)
        return true;
    }

  return false;
}

static std::string
switch_to_g_format (const printf_format_elt *elt)
{
  std::string tfmt = elt->text;

  tfmt.replace (tfmt.rfind (elt->type), 1, "g");

  return tfmt;
}

int
octave_base_stream::do_numeric_printf_conv (std::ostream& os,
                                            const printf_format_elt *elt,
                                            int nsa, int sa_1, int sa_2,
                                            const octave_value& val,
                                            const std::string& who)
{
  int retval = 0;

  const char *fmt = elt->text;

  if (is_nan_or_inf (val))
    {
      double dval = val.double_value ();

      std::string tfmt = fmt;
      std::string::size_type i1, i2;

      tfmt.replace ((i1 = tfmt.rfind (elt->type)), 1, 1, 's');

      if ((i2 = tfmt.rfind ('.')) != std::string::npos && i2 < i1)
        {
          tfmt.erase (i2, i1-i2);
          if (elt->prec == -2)
            nsa--;
        }

      const char *tval;
      if (lo_ieee_isinf (dval))
        {
          if (elt->flags.find ('+') != std::string::npos)
            tval = (dval < 0 ? "-Inf" : "+Inf");
          else
            tval = (dval < 0 ? "-Inf" : "Inf");
        }
      else
        {
          if (elt->flags.find ('+') != std::string::npos)
            tval = (lo_ieee_is_NA (dval) ? "+NA" : "+NaN");
          else
            tval = (lo_ieee_is_NA (dval) ? "NA" : "NaN");
        }

      retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2, tval, who);
    }
  else
    {
      static std::string llmod
        = sizeof (long) == sizeof (int64_t) ? "l" : "ll";

      char type = elt->type;

      switch (type)
        {
        case 'd': case 'i': case 'c':
          if (ok_for_signed_int_conv (val))
            {
              octave_int64 tval = val.int64_scalar_value ();

              // Insert "long" modifier.
              std::string tfmt = fmt;
              tfmt.replace (tfmt.rfind (type), 1, llmod + type);

              retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2,
                                        tval.value (), who);
            }
          else
            {
              std::string tfmt = switch_to_g_format (elt);

              double dval = val.double_value (true);

              retval += do_printf_conv (os, tfmt.c_str (), nsa,
                                        sa_1, sa_2, dval, who);
            }
          break;

        case 'o': case 'x': case 'X': case 'u':
          if (ok_for_unsigned_int_conv (val))
            {
              octave_uint64 tval = val.uint64_scalar_value ();

              // Insert "long" modifier.
              std::string tfmt = fmt;
              tfmt.replace (tfmt.rfind (type), 1, llmod + type);

              retval += do_printf_conv (os, tfmt.c_str (), nsa, sa_1, sa_2,
                                        tval.value (), who);
            }
          else
            {
              std::string tfmt = switch_to_g_format (elt);

              double dval = val.double_value (true);

              retval += do_printf_conv (os, tfmt.c_str (), nsa,
                                        sa_1, sa_2, dval, who);
            }
          break;

        case 'f': case 'e': case 'E':
        case 'g': case 'G':
          {
            double dval = val.double_value (true);

            retval += do_printf_conv (os, fmt, nsa, sa_1, sa_2, dval, who);
          }
          break;

        default:
          // Note: error is member fcn from octave_base_stream, not ::error.
          // This error does not halt execution so "return ..." must exist.
          error ("%s: invalid format specifier", who.c_str ());
          return -1;
          break;
        }
    }

  return retval;
}

int
octave_base_stream::do_printf (printf_format_list& fmt_list,
                               const octave_value_list& args,
                               const std::string& who)
{
  int retval = 0;

  octave_idx_type nconv = fmt_list.num_conversions ();

  std::ostream *osp = output_stream ();

  if (! osp)
    invalid_operation (who, "writing");
  else
    {
      std::ostream& os = *osp;

      const printf_format_elt *elt = fmt_list.first ();

      printf_value_cache val_cache (args, who);

      for (;;)
        {
          octave_quit ();

          if (! elt)
            ::error ("%s: internal error handling format", who.c_str ());

          // NSA is the number of 'star' args to convert.
          int nsa = (elt->fw == -2) + (elt->prec == -2);

          int sa_1 = 0;
          int sa_2 = 0;

          if (nsa > 0)
            {
              sa_1 = val_cache.int_value ();

              if (! val_cache)
                break;
              else
                {
                  if (nsa > 1)
                    {
                      sa_2 = val_cache.int_value ();

                      if (! val_cache)
                        break;
                    }
                }
            }

          if (elt->type == '%')
            {
              os << "%";
              retval++;
            }
          else if (elt->args == 0 && elt->text)
            {
              os << elt->text;
              retval += strlen (elt->text);
            }
          else if (elt->type == 's' || elt->type == 'c')
            {
              octave_value val = val_cache.get_next_value (elt->type);

              if (val_cache)
                {
                  if (val.is_string ())
                    {
                      std::string sval = val.string_value ();

                      retval += do_printf_string (os, elt, nsa, sa_1,
                                                  sa_2, sval, who);
                    }
                  else
                    retval += do_numeric_printf_conv (os, elt, nsa, sa_1,
                                                      sa_2, val, who);
                }
              else
                break;
            }
          else
            {
              octave_value val = val_cache.get_next_value ();

              if (val_cache)
                retval += do_numeric_printf_conv (os, elt, nsa, sa_1,
                                                  sa_2, val, who);
              else
                break;
            }

          if (! os)
            {
              error ("%s: write error", who.c_str ());
              break;
            }

          elt = fmt_list.next (nconv > 0 && ! val_cache.exhausted ());

          if (! elt || (val_cache.exhausted () && elt->args > 0))
            break;
        }
    }

  return retval;
}

int
octave_base_stream::printf (const std::string& fmt,
                            const octave_value_list& args,
                            const std::string& who)
{
  printf_format_list fmt_list (fmt);

  if (fmt_list.num_conversions () == -1)
    ::error ("%s: invalid format specified", who.c_str ());

  return do_printf (fmt_list, args, who);
}

int
octave_base_stream::puts (const std::string& s, const std::string& who)
{
  int retval = -1;

  std::ostream *osp = output_stream ();

  if (! osp)
    invalid_operation (who, "writing");
  else
    {
      std::ostream& os = *osp;

      os << s;

      if (! os)
        error ("%s: write error", who.c_str ());
      else
        {
          // FIXME: why does this seem to be necessary?
          // Without it, output from a loop like
          //
          //   for i = 1:100, fputs (stdout, "foo\n"); endfor
          //
          // doesn't seem to go to the pager immediately.
          os.flush ();

          if (os)
            retval = 0;
          else
            error ("%s: write error", who.c_str ());
        }
    }

  return retval;
}

// Return current error message for this stream.

std::string
octave_base_stream::error (bool clear_err, int& err_num)
{
  err_num = fail ? -1 : 0;

  std::string tmp = errmsg;

  if (clear_err)
    clear ();

  return tmp;
}

void
octave_base_stream::invalid_operation (const std::string& who, const char *rw)
{
  // Note: This calls the member fcn error, not ::error from error.h.
  error (who, std::string ("stream not open for ") + rw);
}

octave_stream::octave_stream (octave_base_stream *bs)
  : rep (bs)
{
  if (rep)
    rep->count = 1;
}

octave_stream::~octave_stream (void)
{
  if (rep && --rep->count == 0)
    delete rep;
}

octave_stream::octave_stream (const octave_stream& s)
  : rep (s.rep)
{
  if (rep)
    rep->count++;
}

octave_stream&
octave_stream::operator = (const octave_stream& s)
{
  if (rep != s.rep)
    {
      if (rep && --rep->count == 0)
        delete rep;

      rep = s.rep;

      if (rep)
        rep->count++;
    }

  return *this;
}

int
octave_stream::flush (void)
{
  int retval = -1;

  if (stream_ok ())
    retval = rep->flush ();

  return retval;
}

std::string
octave_stream::getl (octave_idx_type max_len, bool& err, const std::string& who)
{
  std::string retval;

  if (stream_ok ())
    retval = rep->getl (max_len, err, who);

  return retval;
}

std::string
octave_stream::getl (const octave_value& tc_max_len, bool& err,
                     const std::string& who)
{
  err = false;

  int conv_err = 0;

  int max_len = -1;

  if (tc_max_len.is_defined ())
    {
      max_len = convert_to_valid_int (tc_max_len, conv_err);

      if (conv_err || max_len < 0)
        {
          err = true;
          ::error ("%s: invalid maximum length specified", who.c_str ());
        }
    }

  return getl (max_len, err, who);
}

std::string
octave_stream::gets (octave_idx_type max_len, bool& err, const std::string& who)
{
  std::string retval;

  if (stream_ok ())
    retval = rep->gets (max_len, err, who);

  return retval;
}

std::string
octave_stream::gets (const octave_value& tc_max_len, bool& err,
                     const std::string& who)
{
  err = false;

  int conv_err = 0;

  int max_len = -1;

  if (tc_max_len.is_defined ())
    {
      max_len = convert_to_valid_int (tc_max_len, conv_err);

      if (conv_err || max_len < 0)
        {
          err = true;
          ::error ("%s: invalid maximum length specified", who.c_str ());
        }
    }

  return gets (max_len, err, who);
}

off_t
octave_stream::skipl (off_t count, bool& err, const std::string& who)
{
  off_t retval = -1;

  if (stream_ok ())
    retval = rep->skipl (count, err, who);

  return retval;
}

off_t
octave_stream::skipl (const octave_value& tc_count, bool& err,
                      const std::string& who)
{
  err = false;

  int conv_err = 0;

  int count = 1;

  if (tc_count.is_defined ())
    {
      if (tc_count.is_scalar_type () && xisinf (tc_count.scalar_value ()))
        count = -1;
      else
        {
          count = convert_to_valid_int (tc_count, conv_err);

          if (conv_err || count < 0)
            {
              err = true;
              ::error ("%s: invalid number of lines specified", who.c_str ());
            }
        }
    }

  return skipl (count, err, who);
}

int
octave_stream::seek (off_t offset, int origin)
{
  int status = -1;

  if (stream_ok ())
    {
      clearerr ();

      // Find current position so we can return to it if needed.
      off_t orig_pos = rep->tell ();

      // Move to end of file.  If successful, find the offset of the end.
      status = rep->seek (0, SEEK_END);

      if (status == 0)
        {
          off_t eof_pos = rep->tell ();

          if (origin == SEEK_CUR)
            {
              // Move back to original position, otherwise we will be seeking
              // from the end of file which is probably not the original
              // location.
              rep->seek (orig_pos, SEEK_SET);
            }

          // Attempt to move to desired position; may be outside bounds of
          // existing file.
          status = rep->seek (offset, origin);

          if (status == 0)
            {
              // Where are we after moving to desired position?
              off_t desired_pos = rep->tell ();

              // I don't think save_pos can be less than zero,
              // but we'll check anyway...
              if (desired_pos > eof_pos || desired_pos < 0)
                {
                  // Seek outside bounds of file.
                  // Failure should leave position unchanged.
                  rep->seek (orig_pos, SEEK_SET);

                  status = -1;
                }
            }
          else
            {
              // Seeking to the desired position failed.
              // Move back to original position and return failure status.
              rep->seek (orig_pos, SEEK_SET);

              status = -1;
            }
        }
    }

  return status;
}

int
octave_stream::seek (const octave_value& tc_offset,
                     const octave_value& tc_origin)
{
  int retval = -1;

  // FIXME: should we have octave_value methods that handle off_t explicitly?
  octave_int64 val = tc_offset.xint64_scalar_value ("fseek: invalid value for offset");
  off_t xoffset = val.value ();

  int conv_err = 0;

  int origin = SEEK_SET;

  if (tc_origin.is_string ())
    {
      std::string xorigin = tc_origin.string_value ("fseek: invalid value for origin");

      if (xorigin == "bof")
        origin = SEEK_SET;
      else if (xorigin == "cof")
        origin = SEEK_CUR;
      else if (xorigin == "eof")
        origin = SEEK_END;
      else
        conv_err = -1;
    }
  else
    {
      int xorigin = convert_to_valid_int (tc_origin, conv_err);

      if (! conv_err)
        {
          if (xorigin == -1)
            origin = SEEK_SET;
          else if (xorigin == 0)
            origin = SEEK_CUR;
          else if (xorigin == 1)
            origin = SEEK_END;
          else
            conv_err = -1;
        }
    }

  if (conv_err)
    ::error ("fseek: invalid value for origin");

  retval = seek (xoffset, origin);

  if (retval != 0)
    // Note: error is member fcn from octave_stream, not ::error.
    error ("fseek: failed to seek to requested position");

  return retval;
}

off_t
octave_stream::tell (void)
{
  off_t retval = -1;

  if (stream_ok ())
    retval = rep->tell ();

  return retval;
}

int
octave_stream::rewind (void)
{
  return seek (0, SEEK_SET);
}

bool
octave_stream::is_open (void) const
{
  bool retval = false;

  if (stream_ok ())
    retval = rep->is_open ();

  return retval;
}

void
octave_stream::close (void)
{
  if (stream_ok ())
    rep->close ();
}

// FIXME: maybe these should be defined in lo-ieee.h?

template <typename T>
static inline bool
is_old_NA (T)
{
  return false;
}

template <>
inline bool
is_old_NA<double> (double val)
{
  return __lo_ieee_is_old_NA (val);
}

template <typename T>
static inline T
replace_old_NA (T val)
{
  return val;
}

template <>
inline double
replace_old_NA<double> (double val)
{
  return __lo_ieee_replace_old_NA (val);
}

template <typename SRC_T, typename DST_T>
static octave_value
convert_and_copy (std::list<void *>& input_buf_list,
                  octave_idx_type input_buf_elts,
                  octave_idx_type elts_read,
                  octave_idx_type nr, octave_idx_type nc, bool swap,
                  bool do_float_fmt_conv, bool do_NA_conv,
                  oct_mach_info::float_format from_flt_fmt)
{
  typedef typename DST_T::element_type dst_elt_type;

  DST_T conv (dim_vector (nr, nc));

  dst_elt_type *conv_data = conv.fortran_vec ();

  octave_idx_type j = 0;

  for (std::list<void *>::const_iterator it = input_buf_list.begin ();
       it != input_buf_list.end (); it++)
    {
      SRC_T *data = static_cast<SRC_T *> (*it);

      if (swap || do_float_fmt_conv)
        {
          if (do_NA_conv)
            {
              for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read;
                   i++, j++)
                {
                  if (swap)
                    swap_bytes<sizeof (SRC_T)> (&data[i]);
                  else if (do_float_fmt_conv)
                    do_float_format_conversion (&data[i], sizeof (SRC_T),
                                                1, from_flt_fmt,
                                                oct_mach_info::native_float_format ());

                  dst_elt_type tmp (data[i]);

                  if (is_old_NA (tmp))
                    tmp = replace_old_NA (tmp);

                  conv_data[j] = tmp;
                }
            }
          else
            {
              for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read;
                   i++, j++)
                {
                  if (swap)
                    swap_bytes<sizeof (SRC_T)> (&data[i]);
                  else if (do_float_fmt_conv)
                    do_float_format_conversion (&data[i], sizeof (SRC_T),
                                                1, from_flt_fmt,
                                                oct_mach_info::native_float_format ());

                  conv_data[j] = data[i];
                }
            }
        }
      else
        {
          if (do_NA_conv)
            {
              for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read;
                   i++, j++)
                {
                  dst_elt_type tmp (data[i]);

                  if (is_old_NA (tmp))
                    tmp = replace_old_NA (tmp);

                  conv_data[j] = tmp;
                }
            }
          else
            {
              for (octave_idx_type i = 0; i < input_buf_elts && j < elts_read;
                   i++, j++)
                conv_data[j] = data[i];
            }
        }

      delete [] data;
    }

  input_buf_list.clear ();

  for (octave_idx_type i = elts_read; i < nr * nc; i++)
    conv_data[i] = dst_elt_type (0);

  return conv;
}

typedef octave_value (*conv_fptr)
  (std::list<void *>& input_buf_list, octave_idx_type input_buf_elts,
   octave_idx_type elts_read, octave_idx_type nr, octave_idx_type nc,
   bool swap, bool do_float_fmt_conv, bool do_NA_conv,
   oct_mach_info::float_format from_flt_fmt);

#define TABLE_ELT(T, U, V, W) \
  conv_fptr_table[oct_data_conv::T][oct_data_conv::U] = convert_and_copy<V, W>

#define FILL_TABLE_ROW(T, V) \
  TABLE_ELT (T, dt_int8, V, int8NDArray); \
  TABLE_ELT (T, dt_uint8, V, uint8NDArray); \
  TABLE_ELT (T, dt_int16, V, int16NDArray); \
  TABLE_ELT (T, dt_uint16, V, uint16NDArray); \
  TABLE_ELT (T, dt_int32, V, int32NDArray); \
  TABLE_ELT (T, dt_uint32, V, uint32NDArray); \
  TABLE_ELT (T, dt_int64, V, int64NDArray); \
  TABLE_ELT (T, dt_uint64, V, uint64NDArray); \
  TABLE_ELT (T, dt_single, V, FloatNDArray); \
  TABLE_ELT (T, dt_double, V, NDArray); \
  TABLE_ELT (T, dt_char, V, charNDArray); \
  TABLE_ELT (T, dt_schar, V, charNDArray); \
  TABLE_ELT (T, dt_uchar, V, charNDArray); \
  TABLE_ELT (T, dt_logical, V, boolNDArray);

octave_value
octave_stream::finalize_read (std::list<void *>& input_buf_list,
                              octave_idx_type input_buf_elts,
                              octave_idx_type elts_read,
                              octave_idx_type nr, octave_idx_type nc,
                              oct_data_conv::data_type input_type,
                              oct_data_conv::data_type output_type,
                              oct_mach_info::float_format ffmt)
{
  octave_value retval;

  static bool initialized = false;

  // Table function pointers for return types x read types.

  static conv_fptr conv_fptr_table[oct_data_conv::dt_unknown][14];

  if (! initialized)
    {
      for (int i = 0; i < oct_data_conv::dt_unknown; i++)
        for (int j = 0; j < 14; j++)
          conv_fptr_table[i][j] = 0;

      FILL_TABLE_ROW (dt_int8, int8_t);
      FILL_TABLE_ROW (dt_uint8, uint8_t);
      FILL_TABLE_ROW (dt_int16, int16_t);
      FILL_TABLE_ROW (dt_uint16, uint16_t);
      FILL_TABLE_ROW (dt_int32, int32_t);
      FILL_TABLE_ROW (dt_uint32, uint32_t);
      FILL_TABLE_ROW (dt_int64, int64_t);
      FILL_TABLE_ROW (dt_uint64, uint64_t);
      FILL_TABLE_ROW (dt_single, float);
      FILL_TABLE_ROW (dt_double, double);
      FILL_TABLE_ROW (dt_char, char);
      FILL_TABLE_ROW (dt_schar, signed char);
      FILL_TABLE_ROW (dt_uchar, unsigned char);
      FILL_TABLE_ROW (dt_logical, bool);

      initialized = true;
    }

  bool swap = false;

  if (ffmt == oct_mach_info::flt_fmt_unknown)
    ffmt = float_format ();

  if (oct_mach_info::words_big_endian ())
    swap = (ffmt == oct_mach_info::flt_fmt_ieee_little_endian);
  else
    swap = (ffmt == oct_mach_info::flt_fmt_ieee_big_endian);

  bool do_float_fmt_conv = ((input_type == oct_data_conv::dt_double
                             || input_type == oct_data_conv::dt_single)
                            && ffmt != float_format ());

  bool do_NA_conv = (output_type == oct_data_conv::dt_double);

  switch (output_type)
    {
    case oct_data_conv::dt_int8:
    case oct_data_conv::dt_uint8:
    case oct_data_conv::dt_int16:
    case oct_data_conv::dt_uint16:
    case oct_data_conv::dt_int32:
    case oct_data_conv::dt_uint32:
    case oct_data_conv::dt_int64:
    case oct_data_conv::dt_uint64:
    case oct_data_conv::dt_single:
    case oct_data_conv::dt_double:
    case oct_data_conv::dt_char:
    case oct_data_conv::dt_schar:
    case oct_data_conv::dt_uchar:
    case oct_data_conv::dt_logical:
      {
        conv_fptr fptr = conv_fptr_table[input_type][output_type];

        retval = fptr (input_buf_list, input_buf_elts, elts_read,
                       nr, nc, swap, do_float_fmt_conv, do_NA_conv, ffmt);
      }
      break;

    default:
      ::error ("read: invalid type specification");
    }

  return retval;
}

octave_value
octave_stream::read (const Array<double>& size, octave_idx_type block_size,
                     oct_data_conv::data_type input_type,
                     oct_data_conv::data_type output_type,
                     octave_idx_type skip, oct_mach_info::float_format ffmt,
                     octave_idx_type& count)
{
  octave_value retval;

  octave_idx_type nr = -1;
  octave_idx_type nc = -1;

  bool one_elt_size_spec = false;

  if (! stream_ok ())
    return retval;

  // FIXME: we may eventually want to make this extensible.

  // FIXME: we need a better way to ensure that this
  // numbering stays consistent with the order of the elements in the
  // data_type enum in the oct_data_conv class.

  // Expose this in a future version?
  octave_idx_type char_count = 0;

  count = 0;

  try
    {
      get_size (size, nr, nc, one_elt_size_spec, "fread");
    }
  catch (const octave_execution_exception&)
    {
      invalid_operation ("fread", "reading");
    }

  octave_idx_type elts_to_read;

  if (one_elt_size_spec)
    {
      // If NR == 0, Matlab returns [](0x0).

      // If NR > 0, the result will be a column vector with the given
      // number of rows.

      // If NR < 0, then we have Inf and the result will be a column
      // vector but we have to wait to see how big NR will be.

      if (nr == 0)
        nr = nc = 0;
      else
        nc = 1;
    }
  else
    {
      // Matlab returns [] even if there are two elements in the size
      // specification and one is nonzero.

      // If NC < 0 we have [NR, Inf] and we'll wait to decide how big NC
      // should be.

      if (nr == 0 || nc == 0)
        nr = nc = 0;
    }

  // FIXME: Ensure that this does not overflow.
  //        Maybe try comparing nr * nc computed in double with
  //        std::numeric_limits<octave_idx_type>::max ();
  elts_to_read = nr * nc;

  bool read_to_eof = elts_to_read < 0;

  octave_idx_type input_buf_elts = -1;

  if (skip == 0)
    {
      if (read_to_eof)
        input_buf_elts = 1024 * 1024;
      else
        input_buf_elts = elts_to_read;
    }
  else
    input_buf_elts = block_size;

  octave_idx_type input_elt_size
    = oct_data_conv::data_type_size (input_type);

  octave_idx_type input_buf_size = input_buf_elts * input_elt_size;

  assert (input_buf_size >= 0);

  // Must also work and return correct type object
  // for 0 elements to read.
  std::istream *isp = input_stream ();

  if (! isp)
    error ("fread: invalid input stream");
  else
    {
      std::istream& is = *isp;

      std::list <void *> input_buf_list;

      while (is && ! is.eof ()
             && (read_to_eof || count < elts_to_read))
        {
          if (! read_to_eof)
            {
              octave_idx_type remaining_elts = elts_to_read - count;

              if (remaining_elts < input_buf_elts)
                input_buf_size = remaining_elts * input_elt_size;
            }

          char *input_buf = new char [input_buf_size];

          is.read (input_buf, input_buf_size);

          size_t gcount = is.gcount ();

          char_count += gcount;

          octave_idx_type nel = gcount / input_elt_size;

          count += nel;

          input_buf_list.push_back (input_buf);

          if (is && skip != 0 && nel == block_size)
            {
              // Seek to skip.
              // If skip would move past EOF, position at EOF.

              off_t orig_pos = tell ();

              seek (0, SEEK_END);

              off_t eof_pos = tell ();

              // Is it possible for this to fail to return us to
              // the original position?
              seek (orig_pos, SEEK_SET);

              off_t remaining = eof_pos - orig_pos;

              if (remaining < skip)
                seek (0, SEEK_END);
              else
                seek (skip, SEEK_CUR);

              if (! is)
                break;
            }
        }

      if (read_to_eof)
        {
          if (nc < 0)
            {
              nc = count / nr;

              if (count % nr != 0)
                nc++;
            }
          else
            nr = count;
        }
      else if (count == 0)
        {
          nr = 0;
          nc = 0;
        }
      else if (count != nr * nc)
        {
          if (count % nr != 0)
            nc = count / nr + 1;
          else
            nc = count / nr;

          if (count < nr)
            nr = count;
        }

      retval = finalize_read (input_buf_list, input_buf_elts, count,
                              nr, nc, input_type, output_type, ffmt);
    }

  return retval;
}

octave_idx_type
octave_stream::write (const octave_value& data, octave_idx_type block_size,
                      oct_data_conv::data_type output_type,
                      octave_idx_type skip, oct_mach_info::float_format flt_fmt)
{
  octave_idx_type retval = -1;

  if (! stream_ok ())
    invalid_operation ("fwrite", "writing");
  else
    {
      if (flt_fmt == oct_mach_info::flt_fmt_unknown)
        flt_fmt = float_format ();

      octave_idx_type status = data.write (*this, block_size, output_type,
                                           skip, flt_fmt);

      if (status < 0)
        error ("fwrite: write error");
      else
        retval = status;
    }

  return retval;
}

template <typename T, typename V>
static void
convert_chars (const void *data, void *conv_data, octave_idx_type n_elts)
{
  const T *tt_data = static_cast<const T *> (data);

  V *vt_data = static_cast<V *> (conv_data);

  for (octave_idx_type i = 0; i < n_elts; i++)
    vt_data[i] = tt_data[i];
}

template <typename T, typename V>
static void
convert_ints (const T *data, void *conv_data, octave_idx_type n_elts,
              bool swap)
{
  typedef typename V::val_type val_type;

  val_type *vt_data = static_cast<val_type *> (conv_data);

  for (octave_idx_type i = 0; i < n_elts; i++)
    {
      // Yes, we want saturation semantics when converting to an integer type.
      V val (data[i]);

      vt_data[i] = val.value ();

      if (swap)
        swap_bytes<sizeof (val_type)> (&vt_data[i]);
    }
}

template <typename T>
class ultimate_element_type
{
public:
  typedef T type;
};

template <typename T>
class ultimate_element_type<octave_int<T> >
{
public:
  typedef T type;
};

template <typename T>
static bool
convert_data (const T *data, void *conv_data, octave_idx_type n_elts,
              oct_data_conv::data_type output_type,
              oct_mach_info::float_format flt_fmt)
{
  bool retval = true;

  bool swap
    = ((oct_mach_info::words_big_endian ()
        && flt_fmt == oct_mach_info::flt_fmt_ieee_little_endian)
       || flt_fmt == oct_mach_info::flt_fmt_ieee_big_endian);

  bool do_float_conversion =  flt_fmt != oct_mach_info::float_format ();

  typedef typename ultimate_element_type<T>::type ult_elt_type;

  switch (output_type)
    {
    case oct_data_conv::dt_char:
      convert_chars<ult_elt_type, char> (data, conv_data, n_elts);
      break;

    case oct_data_conv::dt_schar:
      convert_chars<ult_elt_type, signed char> (data, conv_data, n_elts);
      break;

    case oct_data_conv::dt_uchar:
      convert_chars<ult_elt_type, unsigned char> (data, conv_data, n_elts);
      break;

    case oct_data_conv::dt_int8:
      convert_ints<T, octave_int8> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_uint8:
      convert_ints<T, octave_uint8> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_int16:
      convert_ints<T, octave_int16> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_uint16:
      convert_ints<T, octave_uint16> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_int32:
      convert_ints<T, octave_int32> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_uint32:
      convert_ints<T, octave_uint32> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_int64:
      convert_ints<T, octave_int64> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_uint64:
      convert_ints<T, octave_uint64> (data, conv_data, n_elts, swap);
      break;

    case oct_data_conv::dt_single:
      {
        float *vt_data = static_cast<float *> (conv_data);

        for (octave_idx_type i = 0; i < n_elts; i++)
          {
            vt_data[i] = data[i];

            if (do_float_conversion)
              do_float_format_conversion (&vt_data[i], 1, flt_fmt);
          }
      }
      break;

    case oct_data_conv::dt_double:
      {
        double *vt_data = static_cast<double *> (conv_data);

        for (octave_idx_type i = 0; i < n_elts; i++)
          {
            vt_data[i] = data[i];

            if (do_float_conversion)
              do_double_format_conversion (&vt_data[i], 1, flt_fmt);
          }
      }
      break;

    default:
      ::error ("write: invalid type specification");
    }

  return retval;
}

bool
octave_stream::write_bytes (const void *data, size_t nbytes)
{
  bool status = false;

  std::ostream *osp = output_stream ();

  if (osp)
    {
      std::ostream& os = *osp;

      if (os)
        {
          os.write (static_cast<const char *> (data), nbytes);

          if (os)
            status = true;
        }
    }

  return status;
}

bool
octave_stream::skip_bytes (size_t skip)
{
  bool status = false;

  std::ostream *osp = output_stream ();

  if (! osp)
    return false;

  std::ostream& os = *osp;

  // Seek to skip when inside bounds of existing file.
  // Otherwise, write NUL to skip.
  off_t orig_pos = tell ();

  seek (0, SEEK_END);

  off_t eof_pos = tell ();

  // Is it possible for this to fail to return us to the original position?
  seek (orig_pos, SEEK_SET);

  size_t remaining = eof_pos - orig_pos;

  if (remaining < skip)
    {
      seek (0, SEEK_END);

      // FIXME: probably should try to write larger blocks...
      unsigned char zero = 0;
      for (size_t j = 0; j < skip - remaining; j++)
        os.write (reinterpret_cast<const char *> (&zero), 1);
    }
  else
    seek (skip, SEEK_CUR);

  if (os)
    status = true;

  return status;
}

template <typename T>
octave_idx_type
octave_stream::write (const Array<T>& data, octave_idx_type block_size,
                      oct_data_conv::data_type output_type,
                      octave_idx_type skip,
                      oct_mach_info::float_format flt_fmt)
{
  bool swap = ((oct_mach_info::words_big_endian ()
                && flt_fmt == oct_mach_info::flt_fmt_ieee_little_endian)
               || flt_fmt == oct_mach_info::flt_fmt_ieee_big_endian);

  bool do_data_conversion = (swap || ! is_equivalent_type<T> (output_type)
                             || flt_fmt != oct_mach_info::float_format ());

  octave_idx_type nel = data.numel ();

  octave_idx_type chunk_size;

  if (skip != 0)
    chunk_size = block_size;
  else if (do_data_conversion)
    chunk_size = 1024 * 1024;
  else
    chunk_size = nel;

  octave_idx_type i = 0;

  const T *pdata = data.data ();

  while (i < nel)
    {
      if (skip != 0)
        {
          if (! skip_bytes (skip))
            return -1;
        }

      octave_idx_type remaining_nel = nel - i;

      if (chunk_size > remaining_nel)
        chunk_size = remaining_nel;

      bool status = false;

      if (do_data_conversion)
        {
          size_t output_size
            = chunk_size * oct_data_conv::data_type_size (output_type);

          OCTAVE_LOCAL_BUFFER (unsigned char, conv_data, output_size);

          status = convert_data (&pdata[i], conv_data, chunk_size,
                                 output_type, flt_fmt);

          if (status)
            status = write_bytes (conv_data, output_size);
        }
      else
        status = write_bytes (pdata, sizeof (T) * chunk_size);

      if (! status)
        return -1;

      i += chunk_size;
    }

  return nel;
}

#define INSTANTIATE_WRITE(T) \
  template \
  octave_idx_type \
  octave_stream::write (const Array<T>& data, octave_idx_type block_size, \
                        oct_data_conv::data_type output_type, \
                        octave_idx_type skip, \
                        oct_mach_info::float_format flt_fmt)

INSTANTIATE_WRITE (octave_int8);
INSTANTIATE_WRITE (octave_uint8);
INSTANTIATE_WRITE (octave_int16);
INSTANTIATE_WRITE (octave_uint16);
INSTANTIATE_WRITE (octave_int32);
INSTANTIATE_WRITE (octave_uint32);
INSTANTIATE_WRITE (octave_int64);
INSTANTIATE_WRITE (octave_uint64);
INSTANTIATE_WRITE (int8_t);
INSTANTIATE_WRITE (uint8_t);
INSTANTIATE_WRITE (int16_t);
INSTANTIATE_WRITE (uint16_t);
INSTANTIATE_WRITE (int32_t);
INSTANTIATE_WRITE (uint32_t);
INSTANTIATE_WRITE (int64_t);
INSTANTIATE_WRITE (uint64_t);
INSTANTIATE_WRITE (bool);
#if defined (OCTAVE_HAVE_OVERLOAD_CHAR_INT8_TYPES)
INSTANTIATE_WRITE (char);
#endif
INSTANTIATE_WRITE (float);
INSTANTIATE_WRITE (double);

octave_value
octave_stream::scanf (const std::string& fmt, const Array<double>& size,
                      octave_idx_type& count, const std::string& who)
{
  octave_value retval;

  if (stream_ok ())
    retval = rep->scanf (fmt, size, count, who);

  return retval;
}

octave_value
octave_stream::scanf (const octave_value& fmt, const Array<double>& size,
                      octave_idx_type& count, const std::string& who)
{
  octave_value retval = Matrix ();

  if (fmt.is_string ())
    {
      std::string sfmt = fmt.string_value ();

      if (fmt.is_sq_string ())
        sfmt = do_string_escapes (sfmt);

      retval = scanf (sfmt, size, count, who);
    }
  else
    {
      // Note: error is member fcn from octave_stream, not ::error.
      error (who + ": format must be a string");
    }

  return retval;
}

octave_value_list
octave_stream::oscanf (const std::string& fmt, const std::string& who)
{
  octave_value_list retval;

  if (stream_ok ())
    retval = rep->oscanf (fmt, who);

  return retval;
}

octave_value_list
octave_stream::oscanf (const octave_value& fmt, const std::string& who)
{
  octave_value_list retval;

  if (fmt.is_string ())
    {
      std::string sfmt = fmt.string_value ();

      if (fmt.is_sq_string ())
        sfmt = do_string_escapes (sfmt);

      retval = oscanf (sfmt, who);
    }
  else
    {
      // Note: error is member fcn from octave_stream, not ::error.
      error (who + ": format must be a string");
    }

  return retval;
}

int
octave_stream::printf (const std::string& fmt, const octave_value_list& args,
                       const std::string& who)
{
  int retval = -1;

  if (stream_ok ())
    retval = rep->printf (fmt, args, who);

  return retval;
}

int
octave_stream::printf (const octave_value& fmt, const octave_value_list& args,
                       const std::string& who)
{
  int retval = 0;

  if (fmt.is_string ())
    {
      std::string sfmt = fmt.string_value ();

      if (fmt.is_sq_string ())
        sfmt = do_string_escapes (sfmt);

      retval = printf (sfmt, args, who);
    }
  else
    {
      // Note: error is member fcn from octave_stream, not ::error.
      error (who + ": format must be a string");
    }

  return retval;
}

int
octave_stream::puts (const std::string& s, const std::string& who)
{
  int retval = -1;

  if (stream_ok ())
    retval = rep->puts (s, who);

  return retval;
}

// FIXME: maybe this should work for string arrays too.

int
octave_stream::puts (const octave_value& tc_s, const std::string& who)
{
  int retval = -1;

  if (tc_s.is_string ())
    {
      std::string s = tc_s.string_value ();
      retval = puts (s, who);
    }
  else
    {
      // Note: error is member fcn from octave_stream, not ::error.
      error (who + ": argument must be a string");
    }

  return retval;
}

bool
octave_stream::eof (void) const
{
  int retval = -1;

  if (stream_ok ())
    retval = rep->eof ();

  return retval;
}

std::string
octave_stream::error (bool clear, int& err_num)
{
  std::string retval = "invalid stream object";

  if (stream_ok (false))
    retval = rep->error (clear, err_num);

  return retval;
}

std::string
octave_stream::name (void) const
{
  std::string retval;

  if (stream_ok ())
    retval = rep->name ();

  return retval;
}

int
octave_stream::mode (void) const
{
  int retval = 0;

  if (stream_ok ())
    retval = rep->mode ();

  return retval;
}

oct_mach_info::float_format
octave_stream::float_format (void) const
{
  oct_mach_info::float_format retval = oct_mach_info::flt_fmt_unknown;

  if (stream_ok ())
    retval = rep->float_format ();

  return retval;
}

std::string
octave_stream::mode_as_string (int mode)
{
  std::string retval = "???";
  std::ios::openmode in_mode = static_cast<std::ios::openmode> (mode);

  if (in_mode == std::ios::in)
    retval = "r";
  else if (in_mode == std::ios::out
           || in_mode == (std::ios::out | std::ios::trunc))
    retval = "w";
  else if (in_mode == (std::ios::out | std::ios::app))
    retval = "a";
  else if (in_mode == (std::ios::in | std::ios::out))
    retval = "r+";
  else if (in_mode == (std::ios::in | std::ios::out | std::ios::trunc))
    retval = "w+";
  else if (in_mode == (std::ios::in | std::ios::out | std::ios::ate))
    retval = "a+";
  else if (in_mode == (std::ios::in | std::ios::binary))
    retval = "rb";
  else if (in_mode == (std::ios::out | std::ios::binary)
           || in_mode == (std::ios::out | std::ios::trunc | std::ios::binary))
    retval = "wb";
  else if (in_mode == (std::ios::out | std::ios::app | std::ios::binary))
    retval = "ab";
  else if (in_mode == (std::ios::in | std::ios::out | std::ios::binary))
    retval = "r+b";
  else if (in_mode == (std::ios::in | std::ios::out | std::ios::trunc
                       | std::ios::binary))
    retval = "w+b";
  else if (in_mode == (std::ios::in | std::ios::out | std::ios::ate
                       | std::ios::binary))
    retval = "a+b";

  return retval;
}

octave_stream_list *octave_stream_list::instance = 0;

bool
octave_stream_list::instance_ok (void)
{
  bool retval = true;

  if (! instance)
    {
      instance = new octave_stream_list ();

      if (instance)
        singleton_cleanup_list::add (cleanup_instance);
    }

  if (! instance)
    ::error ("unable to create stream list object!");

  return retval;
}

int
octave_stream_list::insert (octave_stream& os)
{
  return (instance_ok ()) ? instance->do_insert (os) : -1;
}

octave_stream
octave_stream_list::lookup (int fid, const std::string& who)
{
  return (instance_ok ()) ? instance->do_lookup (fid, who) : octave_stream ();
}

octave_stream
octave_stream_list::lookup (const octave_value& fid, const std::string& who)
{
  return (instance_ok ()) ? instance->do_lookup (fid, who) : octave_stream ();
}

int
octave_stream_list::remove (int fid, const std::string& who)
{
  return (instance_ok ()) ? instance->do_remove (fid, who) : -1;
}

int
octave_stream_list::remove (const octave_value& fid, const std::string& who)
{
  return (instance_ok ()) ? instance->do_remove (fid, who) : -1;
}

void
octave_stream_list::clear (bool flush)
{
  if (instance)
    instance->do_clear (flush);
}

string_vector
octave_stream_list::get_info (int fid)
{
  return (instance_ok ()) ? instance->do_get_info (fid) : string_vector ();
}

string_vector
octave_stream_list::get_info (const octave_value& fid)
{
  return (instance_ok ()) ? instance->do_get_info (fid) : string_vector ();
}

std::string
octave_stream_list::list_open_files (void)
{
  return (instance_ok ()) ? instance->do_list_open_files () : "";
}

octave_value
octave_stream_list::open_file_numbers (void)
{
  return (instance_ok ())
         ? instance->do_open_file_numbers () : octave_value ();
}

int
octave_stream_list::get_file_number (const octave_value& fid)
{
  return (instance_ok ()) ? instance->do_get_file_number (fid) : -1;
}

int
octave_stream_list::do_insert (octave_stream& os)
{
  // Insert item with key corresponding to file-descriptor.

  int stream_number = os.file_number ();

  if (stream_number == -1)
    return stream_number;

  // Should we test for
  //
  //  (list.find (stream_number) != list.end ()
  //   && list[stream_number].is_open ())
  //
  // and respond with "error ("internal error: ...")"?  It should not
  // happen except for some bug or if the user has opened a stream with
  // an interpreted command, but closed it directly with a system call
  // in an oct-file; then the kernel knows the fd is free, but Octave
  // does not know.  If it happens, it should not do harm here to simply
  // overwrite this entry, although the wrong entry might have done harm
  // before.

  if (list.size () >= list.max_size ())
    ::error ("could not create file id");

  list[stream_number] = os;

  return stream_number;
}

OCTAVE_NORETURN static
void
err_invalid_file_id (int fid, const std::string& who)
{
  if (who.empty ())
    ::error ("invalid stream number = %d", fid);
  else
    ::error ("%s: invalid stream number = %d", who.c_str (), fid);
}

octave_stream
octave_stream_list::do_lookup (int fid, const std::string& who) const
{
  octave_stream retval;

  if (fid < 0)
    err_invalid_file_id (fid, who);

  if (lookup_cache != list.end () && lookup_cache->first == fid)
    retval = lookup_cache->second;
  else
    {
      ostrl_map::const_iterator iter = list.find (fid);

      if (iter == list.end ())
        err_invalid_file_id (fid, who);

      retval = iter->second;
      lookup_cache = iter;
    }

  return retval;
}

octave_stream
octave_stream_list::do_lookup (const octave_value& fid,
                               const std::string& who) const
{
  int i = get_file_number (fid);

  return do_lookup (i, who);
}

int
octave_stream_list::do_remove (int fid, const std::string& who)
{
  // Can't remove stdin (std::cin), stdout (std::cout), or stderr (std::cerr).
  if (fid < 3)
    err_invalid_file_id (fid, who);

  ostrl_map::iterator iter = list.find (fid);

  if (iter == list.end ())
    err_invalid_file_id (fid, who);

  octave_stream os = iter->second;
  list.erase (iter);
  lookup_cache = list.end ();

  // FIXME: is this check redundant?
  if (! os.is_valid ())
    err_invalid_file_id (fid, who);

  os.close ();

  return 0;
}

int
octave_stream_list::do_remove (const octave_value& fid, const std::string& who)
{
  int retval = -1;

  if (fid.is_string () && fid.string_value () == "all")
    {
      do_clear (false);

      retval = 0;
    }
  else
    {
      int i = get_file_number (fid);

      retval = do_remove (i, who);
    }

  return retval;
}

void
octave_stream_list::do_clear (bool flush)
{
  if (flush)
    {
      // Flush stdout and stderr.
      list[1].flush ();
      list[2].flush ();
    }

  for (ostrl_map::iterator iter = list.begin (); iter != list.end (); )
    {
      int fid = iter->first;
      if (fid < 3)  // Don't delete stdin, stdout, stderr
        {
          iter++;
          continue;
        }

      octave_stream os = iter->second;

      std::string name = os.name ();
      std::transform (name.begin (), name.end (), name.begin (), tolower);

      // FIXME: This test for gnuplot is hardly foolproof.
      if (name.find ("gnuplot") != std::string::npos)
        {
          // Don't close down pipes to gnuplot
          iter++;
          continue;
        }

      // Normal file handle.  Close and delete from list.
      if (os.is_valid ())
        os.close ();

      list.erase (iter++);
    }

  lookup_cache = list.end ();
}

string_vector
octave_stream_list::do_get_info (int fid) const
{
  octave_stream os = do_lookup (fid);

  if (! os.is_valid ())
    ::error ("invalid file id = %d", fid);

  string_vector retval (3);

  retval(0) = os.name ();
  retval(1) = octave_stream::mode_as_string (os.mode ());
  retval(2) = oct_mach_info::float_format_as_string (os.float_format ());

  return retval;
}

string_vector
octave_stream_list::do_get_info (const octave_value& fid) const
{
  int conv_err = 0;

  int int_fid = convert_to_valid_int (fid, conv_err);

  if (conv_err)
    ::error ("file id must be a file object or integer value");

  return do_get_info (int_fid);
}

std::string
octave_stream_list::do_list_open_files (void) const
{
  std::ostringstream buf;

  buf << "\n"
      << "  number  mode  arch       name\n"
      << "  ------  ----  ----       ----\n";

  for (ostrl_map::const_iterator p = list.begin (); p != list.end (); p++)
    {
      octave_stream os = p->second;

      buf << "  "
          << std::setiosflags (std::ios::right)
          << std::setw (4) << p->first << "     "
          // reset necessary in addition to setiosflags since this is one stmt.
          << std::resetiosflags (std::ios::adjustfield)
          << std::setiosflags (std::ios::left)
          << std::setw (3)
          << octave_stream::mode_as_string (os.mode ())
          << "  "
          << std::setw (9)
          << oct_mach_info::float_format_as_string (os.float_format ())
          << "  "
          << os.name () << "\n";
    }

  buf << "\n";

  return buf.str ();
}

octave_value
octave_stream_list::do_open_file_numbers (void) const
{
  Matrix retval (1, list.size (), 0.0);

  int num_open = 0;

  for (ostrl_map::const_iterator p = list.begin (); p != list.end (); p++)
    {
      // Skip stdin, stdout, and stderr.
      if (p->first > 2 && p->second)
        retval(0,num_open++) = p->first;
    }

  retval.resize ((num_open > 0), num_open);

  return retval;
}

int
octave_stream_list::do_get_file_number (const octave_value& fid) const
{
  int retval = -1;

  if (fid.is_string ())
    {
      std::string nm = fid.string_value ();

      for (ostrl_map::const_iterator p = list.begin (); p != list.end (); p++)
        {
          // stdin, stdout, and stderr are unnamed.
          if (p->first > 2)
            {
              octave_stream os = p->second;

              if (os && os.name () == nm)
                {
                  retval = p->first;
                  break;
                }
            }
        }
    }
  else
    {
      int conv_err = 0;

      int int_fid = convert_to_valid_int (fid, conv_err);

      if (conv_err)
        ::error ("file id must be a file object, std::string, or integer value");

      retval = int_fid;
    }

  return retval;
}