view libinterp/corefcn/strfns.cc @ 31605:e88a07dec498 stable

maint: Use macros to begin/end C++ namespaces. * oct-conf-post-public.in.h: Define two macros (OCTAVE_BEGIN_NAMESPACE, OCTAVE_END_NAMESPACE) that can be used to start/end a namespace. * mk-opts.pl, build-env.h, build-env.in.cc, __betainc__.cc, __contourc__.cc, __dsearchn__.cc, __eigs__.cc, __expint__.cc, __ftp__.cc, __gammainc__.cc, __ichol__.cc, __ilu__.cc, __isprimelarge__.cc, __lin_interpn__.cc, __magick_read__.cc, __pchip_deriv__.cc, __qp__.cc, amd.cc, auto-shlib.cc, auto-shlib.h, balance.cc, base-text-renderer.cc, base-text-renderer.h, besselj.cc, bitfcns.cc, bsxfun.cc, c-file-ptr-stream.cc, c-file-ptr-stream.h, call-stack.cc, call-stack.h, ccolamd.cc, cellfun.cc, chol.cc, colamd.cc, colloc.cc, conv2.cc, daspk.cc, dasrt.cc, dassl.cc, data.cc, data.h, debug.cc, defaults.cc, defaults.h, defun-int.h, defun.cc, det.cc, dirfns.cc, display.cc, display.h, dlmread.cc, dmperm.cc, dot.cc, dynamic-ld.cc, dynamic-ld.h, eig.cc, ellipj.cc, environment.cc, environment.h, error.cc, error.h, errwarn.h, event-manager.cc, event-manager.h, event-queue.cc, event-queue.h, fcn-info.cc, fcn-info.h, fft.cc, fft2.cc, fftn.cc, file-io.cc, filter.cc, find.cc, ft-text-renderer.cc, ft-text-renderer.h, gcd.cc, getgrent.cc, getpwent.cc, getrusage.cc, givens.cc, gl-render.cc, gl-render.h, gl2ps-print.cc, gl2ps-print.h, graphics-toolkit.cc, graphics-toolkit.h, graphics.cc, graphics.in.h, gsvd.cc, gtk-manager.cc, gtk-manager.h, hash.cc, help.cc, help.h, hess.cc, hex2num.cc, hook-fcn.cc, hook-fcn.h, input.cc, input.h, interpreter-private.cc, interpreter-private.h, interpreter.cc, interpreter.h, inv.cc, jsondecode.cc, jsonencode.cc, kron.cc, latex-text-renderer.cc, latex-text-renderer.h, load-path.cc, load-path.h, load-save.cc, load-save.h, lookup.cc, ls-ascii-helper.cc, ls-ascii-helper.h, ls-oct-text.cc, ls-utils.cc, ls-utils.h, lsode.cc, lu.cc, mappers.cc, matrix_type.cc, max.cc, mex-private.h, mex.cc, mgorth.cc, nproc.cc, oct-fstrm.cc, oct-fstrm.h, oct-hdf5-types.cc, oct-hdf5-types.h, oct-hist.cc, oct-hist.h, oct-iostrm.cc, oct-iostrm.h, oct-opengl.h, oct-prcstrm.cc, oct-prcstrm.h, oct-procbuf.cc, oct-procbuf.h, oct-process.cc, oct-process.h, oct-stdstrm.h, oct-stream.cc, oct-stream.h, oct-strstrm.cc, oct-strstrm.h, oct-tex-lexer.in.ll, oct-tex-parser.yy, ordqz.cc, ordschur.cc, pager.cc, pager.h, pinv.cc, pow2.cc, pr-flt-fmt.cc, pr-output.cc, procstream.cc, procstream.h, psi.cc, qr.cc, quad.cc, quadcc.cc, qz.cc, rand.cc, rcond.cc, regexp.cc, schur.cc, settings.cc, settings.h, sighandlers.cc, sighandlers.h, sparse-xdiv.cc, sparse-xdiv.h, sparse-xpow.cc, sparse-xpow.h, sparse.cc, spparms.cc, sqrtm.cc, stack-frame.cc, stack-frame.h, stream-euler.cc, strfind.cc, strfns.cc, sub2ind.cc, svd.cc, sylvester.cc, symbfact.cc, syminfo.cc, syminfo.h, symrcm.cc, symrec.cc, symrec.h, symscope.cc, symscope.h, symtab.cc, symtab.h, syscalls.cc, sysdep.cc, sysdep.h, text-engine.cc, text-engine.h, text-renderer.cc, text-renderer.h, time.cc, toplev.cc, tril.cc, tsearch.cc, typecast.cc, url-handle-manager.cc, url-handle-manager.h, urlwrite.cc, utils.cc, utils.h, variables.cc, variables.h, xdiv.cc, xdiv.h, xnorm.cc, xnorm.h, xpow.cc, xpow.h, __delaunayn__.cc, __fltk_uigetfile__.cc, __glpk__.cc, __init_fltk__.cc, __init_gnuplot__.cc, __ode15__.cc, __voronoi__.cc, audiodevinfo.cc, audioread.cc, convhulln.cc, fftw.cc, gzip.cc, mk-build-env-features.sh, mk-builtins.pl, cdef-class.cc, cdef-class.h, cdef-fwd.h, cdef-manager.cc, cdef-manager.h, cdef-method.cc, cdef-method.h, cdef-object.cc, cdef-object.h, cdef-package.cc, cdef-package.h, cdef-property.cc, cdef-property.h, cdef-utils.cc, cdef-utils.h, ov-base.cc, ov-base.h, ov-bool-mat.cc, ov-builtin.h, ov-cell.cc, ov-class.cc, ov-class.h, ov-classdef.cc, ov-classdef.h, ov-complex.cc, ov-fcn-handle.cc, ov-fcn-handle.h, ov-fcn.h, ov-java.cc, ov-java.h, ov-mex-fcn.h, ov-null-mat.cc, ov-oncleanup.cc, ov-struct.cc, ov-typeinfo.cc, ov-typeinfo.h, ov-usr-fcn.cc, ov-usr-fcn.h, ov.cc, ov.h, octave.cc, octave.h, mk-ops.sh, op-b-b.cc, op-b-bm.cc, op-b-sbm.cc, op-bm-b.cc, op-bm-bm.cc, op-bm-sbm.cc, op-cdm-cdm.cc, op-cell.cc, op-chm.cc, op-class.cc, op-cm-cm.cc, op-cm-cs.cc, op-cm-m.cc, op-cm-s.cc, op-cm-scm.cc, op-cm-sm.cc, op-cs-cm.cc, op-cs-cs.cc, op-cs-m.cc, op-cs-s.cc, op-cs-scm.cc, op-cs-sm.cc, op-dm-dm.cc, op-dm-scm.cc, op-dm-sm.cc, op-dm-template.cc, op-dms-template.cc, op-fcdm-fcdm.cc, op-fcm-fcm.cc, op-fcm-fcs.cc, op-fcm-fm.cc, op-fcm-fs.cc, op-fcn.cc, op-fcs-fcm.cc, op-fcs-fcs.cc, op-fcs-fm.cc, op-fcs-fs.cc, op-fdm-fdm.cc, op-fm-fcm.cc, op-fm-fcs.cc, op-fm-fm.cc, op-fm-fs.cc, op-fs-fcm.cc, op-fs-fcs.cc, op-fs-fm.cc, op-fs-fs.cc, op-i16-i16.cc, op-i32-i32.cc, op-i64-i64.cc, op-i8-i8.cc, op-int-concat.cc, op-m-cm.cc, op-m-cs.cc, op-m-m.cc, op-m-s.cc, op-m-scm.cc, op-m-sm.cc, op-mi.cc, op-pm-pm.cc, op-pm-scm.cc, op-pm-sm.cc, op-pm-template.cc, op-range.cc, op-s-cm.cc, op-s-cs.cc, op-s-m.cc, op-s-s.cc, op-s-scm.cc, op-s-sm.cc, op-sbm-b.cc, op-sbm-bm.cc, op-sbm-sbm.cc, op-scm-cm.cc, op-scm-cs.cc, op-scm-m.cc, op-scm-s.cc, op-scm-scm.cc, op-scm-sm.cc, op-sm-cm.cc, op-sm-cs.cc, op-sm-m.cc, op-sm-s.cc, op-sm-scm.cc, op-sm-sm.cc, op-str-m.cc, op-str-s.cc, op-str-str.cc, op-struct.cc, op-ui16-ui16.cc, op-ui32-ui32.cc, op-ui64-ui64.cc, op-ui8-ui8.cc, ops.h, anon-fcn-validator.cc, anon-fcn-validator.h, bp-table.cc, bp-table.h, comment-list.cc, comment-list.h, filepos.h, lex.h, lex.ll, oct-lvalue.cc, oct-lvalue.h, oct-parse.yy, parse.h, profiler.cc, profiler.h, pt-anon-scopes.cc, pt-anon-scopes.h, pt-arg-list.cc, pt-arg-list.h, pt-args-block.cc, pt-args-block.h, pt-array-list.cc, pt-array-list.h, pt-assign.cc, pt-assign.h, pt-binop.cc, pt-binop.h, pt-bp.cc, pt-bp.h, pt-cbinop.cc, pt-cbinop.h, pt-cell.cc, pt-cell.h, pt-check.cc, pt-check.h, pt-classdef.cc, pt-classdef.h, pt-cmd.h, pt-colon.cc, pt-colon.h, pt-const.cc, pt-const.h, pt-decl.cc, pt-decl.h, pt-eval.cc, pt-eval.h, pt-except.cc, pt-except.h, pt-exp.cc, pt-exp.h, pt-fcn-handle.cc, pt-fcn-handle.h, pt-id.cc, pt-id.h, pt-idx.cc, pt-idx.h, pt-jump.h, pt-loop.cc, pt-loop.h, pt-mat.cc, pt-mat.h, pt-misc.cc, pt-misc.h, pt-pr-code.cc, pt-pr-code.h, pt-select.cc, pt-select.h, pt-spmd.cc, pt-spmd.h, pt-stmt.cc, pt-stmt.h, pt-tm-const.cc, pt-tm-const.h, pt-unop.cc, pt-unop.h, pt-vm-eval.cc, pt-walk.cc, pt-walk.h, pt.cc, pt.h, token.cc, token.h, Range.cc, Range.h, idx-vector.cc, idx-vector.h, range-fwd.h, CollocWt.cc, CollocWt.h, aepbalance.cc, aepbalance.h, chol.cc, chol.h, gepbalance.cc, gepbalance.h, gsvd.cc, gsvd.h, hess.cc, hess.h, lo-mappers.cc, lo-mappers.h, lo-specfun.cc, lo-specfun.h, lu.cc, lu.h, oct-convn.cc, oct-convn.h, oct-fftw.cc, oct-fftw.h, oct-norm.cc, oct-norm.h, oct-rand.cc, oct-rand.h, oct-spparms.cc, oct-spparms.h, qr.cc, qr.h, qrp.cc, qrp.h, randgamma.cc, randgamma.h, randmtzig.cc, randmtzig.h, randpoisson.cc, randpoisson.h, schur.cc, schur.h, sparse-chol.cc, sparse-chol.h, sparse-lu.cc, sparse-lu.h, sparse-qr.cc, sparse-qr.h, svd.cc, svd.h, child-list.cc, child-list.h, dir-ops.cc, dir-ops.h, file-ops.cc, file-ops.h, file-stat.cc, file-stat.h, lo-sysdep.cc, lo-sysdep.h, lo-sysinfo.cc, lo-sysinfo.h, mach-info.cc, mach-info.h, oct-env.cc, oct-env.h, oct-group.cc, oct-group.h, oct-password.cc, oct-password.h, oct-syscalls.cc, oct-syscalls.h, oct-time.cc, oct-time.h, oct-uname.cc, oct-uname.h, action-container.cc, action-container.h, base-list.h, cmd-edit.cc, cmd-edit.h, cmd-hist.cc, cmd-hist.h, f77-fcn.h, file-info.cc, file-info.h, lo-array-errwarn.cc, lo-array-errwarn.h, lo-hash.cc, lo-hash.h, lo-ieee.h, lo-regexp.cc, lo-regexp.h, lo-utils.cc, lo-utils.h, oct-base64.cc, oct-base64.h, oct-glob.cc, oct-glob.h, oct-inttypes.h, oct-mutex.cc, oct-mutex.h, oct-refcount.h, oct-shlib.cc, oct-shlib.h, oct-sparse.cc, oct-sparse.h, oct-string.h, octave-preserve-stream-state.h, pathsearch.cc, pathsearch.h, quit.cc, quit.h, unwind-prot.cc, unwind-prot.h, url-transfer.cc, url-transfer.h : Use new macros to begin/end C++ namespaces.
author Rik <rik@octave.org>
date Thu, 01 Dec 2022 14:23:45 -0800
parents c9788d7f6e65
children aac27ad79be6
line wrap: on
line source

////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 1994-2022 The Octave Project Developers
//
// See the file COPYRIGHT.md in the top-level directory of this
// distribution or <https://octave.org/copyright/>.
//
// 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
// <https://www.gnu.org/licenses/>.
//
////////////////////////////////////////////////////////////////////////

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

#include <cctype>

#include <queue>
#include <sstream>

#include "dMatrix.h"
#include "localcharset-wrapper.h"
#include "uniconv-wrappers.h"
#include "unistr-wrappers.h"

#include "Cell.h"
#include "defun.h"
#include "error.h"
#include "errwarn.h"
#include "ov.h"
#include "ovl.h"
#include "unwind-prot.h"
#include "utils.h"

#include "oct-string.h"

OCTAVE_BEGIN_NAMESPACE(octave)

DEFUN (char, args, ,
       doc: /* -*- texinfo -*-
@deftypefn  {} {@var{C} =} char (@var{A})
@deftypefnx {} {@var{C} =} char (@var{A}, @dots{})
@deftypefnx {} {@var{C} =} char (@var{str1}, @var{str2}, @dots{})
@deftypefnx {} {@var{C} =} char (@var{cell_array})
Create a string array from one or more numeric matrices, character
matrices, or cell arrays.

Arguments are concatenated vertically.  The returned values are padded with
blanks as needed to make each row of the string array have the same length.
Empty input strings are significant and will concatenated in the output.

For numerical input, each element is converted to the corresponding ASCII
character.  A range error results if an input is outside the ASCII range
(0-255).

For cell arrays, each element is concatenated separately.  Cell arrays
converted through @code{char} can mostly be converted back with
@code{cellstr}.  For example:

@example
@group
char ([97, 98, 99], "", @{"98", "99", 100@}, "str1", ["ha", "lf"])
   @result{} ["abc "
       "    "
       "98  "
       "99  "
       "d   "
       "str1"
       "half"]
@end group
@end example
@seealso{strvcat, cellstr}
@end deftypefn */)
{
  octave_value retval;

  int nargin = args.length ();

  if (nargin == 0)
    retval = "";
  else if (nargin == 1)
    retval = args(0).convert_to_str (true, true,
                                     args(0).is_dq_string () ? '"' : '\'');
  else
    {
      int n_elts = 0;

      int max_len = 0;

      std::queue<string_vector> args_as_strings;

      for (int i = 0; i < nargin; i++)
        {
          string_vector s = args(i).xstring_vector_value ("char: unable to convert some args to strings");

          if (s.numel () > 0)
            n_elts += s.numel ();
          else
            n_elts += 1;

          int s_max_len = s.max_length ();

          if (s_max_len > max_len)
            max_len = s_max_len;

          args_as_strings.push (s);
        }

      string_vector result (n_elts);

      int k = 0;

      for (int i = 0; i < nargin; i++)
        {
          string_vector s = args_as_strings.front ();
          args_as_strings.pop ();

          int n = s.numel ();

          if (n > 0)
            {
              for (int j = 0; j < n; j++)
                {
                  std::string t = s[j];
                  int t_len = t.length ();

                  if (max_len > t_len)
                    t += std::string (max_len - t_len, ' ');

                  result[k++] = t;
                }
            }
          else
            result[k++] = std::string (max_len, ' ');
        }

      retval = octave_value (result, '\'');
    }

  return retval;
}

/*
%!assert (char (), '')
%!assert (char (100), "d")
%!assert (char (100,100), ["d";"d"])
%!assert (char ({100,100}), ["d";"d"])
%!assert (char ([100,100]), ["dd"])
%!assert (char ({100,{100}}), ["d";"d"])
%!assert (char (100, [], 100), ["d";" ";"d"])
%!assert (char ({100, [], 100}), ["d";" ";"d"])
%!assert (char ({100,{100, {""}}}), ["d";"d";" "])
%!assert (char (["a ";"be"], {"c", 100}), ["a ";"be";"c ";"d "])
%!assert (char ("a", "bb", "ccc"), ["a  "; "bb "; "ccc"])
%!assert (char ([65, 83, 67, 73, 73]), "ASCII")

%!test
%! x = char ("foo", "bar", "foobar");
%! assert (x(1,:), "foo   ");
%! assert (x(2,:), "bar   ");
%! assert (x(3,:), "foobar");
*/

DEFUN (strvcat, args, ,
       doc: /* -*- texinfo -*-
@deftypefn  {} {@var{C} =} strvcat (@var{A})
@deftypefnx {} {@var{C} =} strvcat (@var{A}, @dots{})
@deftypefnx {} {@var{C} =} strvcat (@var{str1}, @var{str2}, @dots{})
@deftypefnx {} {@var{C} =} strvcat (@var{cell_array})
Create a character array from one or more numeric matrices, character
matrices, or cell arrays.

Arguments are concatenated vertically.  The returned values are padded with
blanks as needed to make each row of the string array have the same length.
Unlike @code{char}, empty strings are removed and will not appear in the
output.

For numerical input, each element is converted to the corresponding ASCII
character.  A range error results if an input is outside the ASCII range
(0-255).

For cell arrays, each element is concatenated separately.  Cell arrays
converted through @code{strvcat} can mostly be converted back with
@code{cellstr}.  For example:

@example
@group
strvcat ([97, 98, 99], "", @{"98", "99", 100@}, "str1", ["ha", "lf"])
      @result{} ["abc "
          "98  "
          "99  "
          "d   "
          "str1"
          "half"]
@end group
@end example
@seealso{char, strcat, cstrcat}
@end deftypefn */)
{
  int nargin = args.length ();
  int n_elts = 0;
  std::size_t max_len = 0;
  std::queue<string_vector> args_as_strings;

  for (int i = 0; i < nargin; i++)
    {
      string_vector s = args(i).xstring_vector_value ("strvcat: unable to convert some args to strings");

      std::size_t n = s.numel ();

      // do not count empty strings in calculation of number of elements
      if (n > 0)
        {
          for (std::size_t j = 0; j < n; j++)
            {
              if (! s[j].empty ())
                n_elts++;
            }
        }

      std::size_t s_max_len = s.max_length ();

      if (s_max_len > max_len)
        max_len = s_max_len;

      args_as_strings.push (s);
    }

  string_vector result (n_elts);

  octave_idx_type k = 0;

  for (int i = 0; i < nargin; i++)
    {
      string_vector s = args_as_strings.front ();
      args_as_strings.pop ();

      std::size_t n = s.numel ();

      if (n > 0)
        {
          for (std::size_t j = 0; j < n; j++)
            {
              std::string t = s[j];
              if (t.length () > 0)
                {
                  std::size_t t_len = t.length ();

                  if (max_len > t_len)
                    t += std::string (max_len - t_len, ' ');

                  result[k++] = t;
                }
            }
        }
    }

  // Cannot use ovl.  Relies on overloaded octave_value call.
  return octave_value (result, '\'');
}

/*
%!assert (strvcat (""), "")
%!assert (strvcat (100) == "d")
%!assert (strvcat (100,100), ["d";"d"])
%!assert (strvcat ({100,100}), ["d";"d"])
%!assert (strvcat ([100,100]), ["dd"])
%!assert (strvcat ({100,{100}}), ["d";"d"])
%!assert (strvcat (100, [], 100), ["d";"d"])
%!assert (strvcat ({100, [], 100}), ["d";"d"])
%!assert (strvcat ({100,{100, {""}}}), ["d";"d"])
%!assert (strvcat (["a ";"be"], {"c", 100}), ["a ";"be";"c ";"d "])
%!assert (strvcat ("a", "bb", "ccc"), ["a  "; "bb "; "ccc"])
%!assert (strvcat (), "")
*/

DEFUN (ischar, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} ischar (@var{x})
Return true if @var{x} is a character array.
@seealso{isfloat, isinteger, islogical, isnumeric, isstring, iscellstr, isa}
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  return ovl (args(0).is_string ());
}

/*
%!assert (ischar ("a"), true)
%!assert (ischar (["ab";"cd"]), true)
%!assert (ischar ({"ab"}), false)
%!assert (ischar (1), false)
%!assert (ischar ([1, 2]), false)
%!assert (ischar ([]), false)
%!assert (ischar ([1, 2; 3, 4]), false)
%!assert (ischar (""), true)
%!assert (ischar ("test"), true)
%!assert (ischar (["test"; "ing"]), true)
%!assert (ischar (struct ("foo", "bar")), false)

%!error ischar ()
%!error ischar ("test", 1)
*/

static octave_value
do_strcmp_fcn (const octave_value& arg0, const octave_value& arg1,
               octave_idx_type n, const char *fcn_name,
               bool (*array_op) (const Array<char>&, const Array<char>&,
                                 octave_idx_type),
               bool (*str_op) (const std::string&, const std::string&,
                               std::string::size_type))

{
  octave_value retval;

  bool s1_string = arg0.is_string ();
  bool s1_cell = arg0.iscell ();
  bool s2_string = arg1.is_string ();
  bool s2_cell = arg1.iscell ();

  if (s1_string && s2_string)
    retval = array_op (arg0.char_array_value (), arg1.char_array_value (), n);
  else if ((s1_string && s2_cell) || (s1_cell && s2_string))
    {
      octave_value str_val, cell_val;

      if (s1_string)
        {
          str_val = arg0;
          cell_val = arg1;
        }
      else
        {
          str_val = arg1;
          cell_val = arg0;
        }

      const Cell cell = cell_val.cell_value ();
      const string_vector str = str_val.string_vector_value ();
      octave_idx_type r = str.numel ();

      if (r == 0 || r == 1)
        {
          // Broadcast the string.

          boolNDArray output (cell_val.dims (), false);

          std::string s = (r == 0 ? "" : str[0]);

          if (cell_val.iscellstr ())
            {
              const Array<std::string> cellstr = cell_val.cellstr_value ();
              for (octave_idx_type i = 0; i < cellstr.numel (); i++)
                output(i) = str_op (cellstr(i), s, n);
            }
          else
            {
              // FIXME: should we warn here?
              for (octave_idx_type i = 0; i < cell.numel (); i++)
                {
                  if (cell(i).is_string ())
                    output(i) = str_op (cell(i).string_value (), s, n);
                }
            }

          retval = output;
        }
      else if (r > 1)
        {
          if (cell.numel () == 1)
            {
              // Broadcast the cell.

              const dim_vector dv (r, 1);
              boolNDArray output (dv, false);

              if (cell(0).is_string ())
                {
                  const std::string str2 = cell(0).string_value ();

                  for (octave_idx_type i = 0; i < r; i++)
                    output(i) = str_op (str[i], str2, n);
                }

              retval = output;
            }
          else
            {
              // Must match in all dimensions.

              boolNDArray output (cell.dims (), false);

              if (cell.numel () == r)
                {
                  if (cell_val.iscellstr ())
                    {
                      const Array<std::string> cellstr
                        = cell_val.cellstr_value ();
                      for (octave_idx_type i = 0; i < cellstr.numel (); i++)
                        output(i) = str_op (str[i], cellstr(i), n);
                    }
                  else
                    {
                      // FIXME: should we warn here?
                      for (octave_idx_type i = 0; i < r; i++)
                        {
                          if (cell(i).is_string ())
                            output(i) = str_op (str[i],
                                                cell(i).string_value (), n);
                        }
                    }

                  retval = output;
                }
              else
                retval = false;
            }
        }
    }
  else if (s1_cell && s2_cell)
    {
      octave_value cell1_val, cell2_val;
      octave_idx_type r1 = arg0.numel (), r2;

      if (r1 == 1)
        {
          // Make the singleton cell2.

          cell1_val = arg1;
          cell2_val = arg0;
        }
      else
        {
          cell1_val = arg0;
          cell2_val = arg1;
        }

      const Cell cell1 = cell1_val.cell_value ();
      const Cell cell2 = cell2_val.cell_value ();
      r1 = cell1.numel ();
      r2 = cell2.numel ();

      const dim_vector size1 = cell1.dims ();
      const dim_vector size2 = cell2.dims ();

      boolNDArray output (size1, false);

      if (r2 == 1)
        {
          // Broadcast cell2.

          if (cell2(0).is_string ())
            {
              const std::string str2 = cell2(0).string_value ();

              if (cell1_val.iscellstr ())
                {
                  const Array<std::string> cellstr = cell1_val.cellstr_value ();
                  for (octave_idx_type i = 0; i < cellstr.numel (); i++)
                    output(i) = str_op (cellstr(i), str2, n);
                }
              else
                {
                  // FIXME: should we warn here?
                  for (octave_idx_type i = 0; i < r1; i++)
                    {
                      if (cell1(i).is_string ())
                        {
                          const std::string str1 = cell1(i).string_value ();
                          output(i) = str_op (str1, str2, n);
                        }
                    }
                }
            }
        }
      else
        {
          if (size1 != size2)
            error ("%s: nonconformant cell arrays", fcn_name);

          if (cell1.iscellstr () && cell2.iscellstr ())
            {
              const Array<std::string> cellstr1 = cell1_val.cellstr_value ();
              const Array<std::string> cellstr2 = cell2_val.cellstr_value ();
              for (octave_idx_type i = 0; i < r1; i++)
                output (i) = str_op (cellstr1(i), cellstr2(i), n);
            }
          else
            {
              // FIXME: should we warn here?
              for (octave_idx_type i = 0; i < r1; i++)
                {
                  if (cell1(i).is_string () && cell2(i).is_string ())
                    {
                      const std::string str1 = cell1(i).string_value ();
                      const std::string str2 = cell2(i).string_value ();
                      output(i) = str_op (str1, str2, n);
                    }
                }
            }
        }

      retval = output;
    }
  else
    retval = false;

  return retval;
}


// These are required so that they match the same signature as strncmp
// and strncmpi and can therefore be used in do_strcmp_fcn.

template <typename T, typename T_size_type>
static bool
strcmp_ignore_n (const T& s1, const T& s2, T_size_type)
{ return string::strcmp (s1, s2); }

template <typename T, typename T_size_type>
static bool
strcmpi_ignore_n (const T& s1, const T& s2, T_size_type)
{ return string::strcmpi (s1, s2); }


DEFUN (strcmp, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} strcmp (@var{str1}, @var{str2})
Return 1 if the character strings @var{str1} and @var{str2} are the same,
and 0 otherwise.

If either @var{str1} or @var{str2} is a cell array of strings, then an array
of the same size is returned, containing the values described above for
every member of the cell array.  The other argument may also be a cell
array of strings (of the same size or with only one element), char matrix
or character string.

@strong{Caution:} For compatibility with @sc{matlab}, Octave's strcmp
function returns 1 if the character strings are equal, and 0 otherwise.
This is just the opposite of the corresponding C library function.
@seealso{strcmpi, strncmp, strncmpi}
@end deftypefn */)
{
  if (args.length () != 2)
    print_usage ();

  return ovl (do_strcmp_fcn (args(0), args(1), 0, "strcmp",
                             strcmp_ignore_n, strcmp_ignore_n));
}

/*
%!shared x
%! x = char (zeros (0, 2));
%!assert (strcmp ("", x), false)
%!assert (strcmp (x, ""), false)
%!assert (strcmp (x, x), true)
## %!assert (strcmp ({""}, x), true)
## %!assert (strcmp ({x}, ""), false)
## %!assert (strcmp ({x}, x), true)
## %!assert (strcmp ("", {x}), false)
## %!assert (strcmp (x, {""}), false)
## %!assert (strcmp (x, {x}), true)
## %!assert (strcmp ({x; x}, ""), [false; false])
## %!assert (strcmp ({x; x}, {""}), [false; false])
## %!assert (strcmp ("", {x; x}), [false; false])
## %!assert (strcmp ({""}, {x; x}), [false; false])
%!assert (strcmp ({"foo"}, x), false)
%!assert (strcmp ({"foo"}, "foo"), true)
%!assert (strcmp ({"foo"}, x), false)
%!assert (strcmp (x, {"foo"}), false)
%!assert (strcmp ("foo", {"foo"}), true)
%!assert (strcmp (x, {"foo"}), false)
%!shared y
%! y = char (zeros (2, 0));
%!assert (strcmp ("", y), false)
%!assert (strcmp (y, ""), false)
%!assert (strcmp (y, y), true)
%!assert (strcmp ({""}, y), [true; true])
%!assert (strcmp ({y}, ""), true)
%!assert (strcmp ({y}, y), [true; true])
%!assert (strcmp ("", {y}), true)
%!assert (strcmp (y, {""}), [true; true])
%!assert (strcmp (y, {y}), [true; true])
%!assert (strcmp ({y; y}, ""), [true; true])
%!assert (strcmp ({y; y}, {""}), [true; true])
%!assert (strcmp ("", {y; y}), [true; true])
%!assert (strcmp ({""}, {y; y}), [true; true])
%!assert (strcmp ({"foo"}, y), [false; false])
%!assert (strcmp ({"foo"}, y), [false; false])
%!assert (strcmp (y, {"foo"}), [false; false])
%!assert (strcmp (y, {"foo"}), [false; false])
%!assert (strcmp ("foobar", "foobar"), true)
%!assert (strcmp ("foobar", "fooBar"), false)
%!assert (strcmp ("fooba", "foobar"), false)

%!error strcmp ()
%!error strcmp ("foo", "bar", 3)
*/

DEFUN (strncmp, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} strncmp (@var{str1}, @var{str2}, @var{n})
Return 1 if the first @var{n} characters of strings @var{str1} and @var{str2}
are the same, and 0 otherwise.

@example
@group
strncmp ("abce", "abcd", 3)
      @result{} 1
@end group
@end example

If either @var{str1} or @var{str2} is a cell array of strings, then an array
of the same size is returned, containing the values described above for
every member of the cell array.  The other argument may also be a cell
array of strings (of the same size or with only one element), char matrix
or character string.

@example
@group
strncmp ("abce", @{"abcd", "bca", "abc"@}, 3)
     @result{} [1, 0, 1]
@end group
@end example

@strong{Caution:} For compatibility with @sc{matlab}, Octave's strncmp
function returns 1 if the character strings are equal, and 0 otherwise.
This is just the opposite of the corresponding C library function.
@seealso{strncmpi, strcmp, strcmpi}
@end deftypefn */)
{
  if (args.length () != 3)
    print_usage ();

  octave_idx_type n = args(2).idx_type_value ();

  if (n > 0)
    return ovl (do_strcmp_fcn (args(0), args(1), n, "strncmp",
                               string::strncmp,
                               string::strncmp));
  else
    error ("strncmp: N must be greater than 0");
}

/*
%!assert (strncmp ("abce", "abc", 3), true)
%!assert (strncmp ("abce", "aBc", 3), false)
%!assert (strncmp (100, 100, 1), false)
%!assert (strncmp ("abce", {"abcd", "bca", "abc"}, 3), logical ([1, 0, 1]))
%!assert (strncmp ("abc",  {"abcd", "bca", "abc"}, 4), logical ([0, 0, 1]))
%!assert (strncmp ({"abcd", "bca", "abc"},"abce", 3), logical ([1, 0, 1]))
%!assert (strncmp ({"abcd", "bca", "abc"},{"abcd", "bca", "abe"}, 3),
%!        logical ([1, 1, 0]))
%!assert (strncmp ("abc", {"abcd", 10}, 2), logical ([1, 0]))

%!assert <*54373> (strncmp ("abc", "abc", 100))

%!error strncmp ()
%!error strncmp ("abc", "def")
*/

DEFUNX ("strcmpi", Fstrcmpi, args, ,
        doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} strcmpi (@var{str1}, @var{str2})
Return 1 if the character strings @var{str1} and @var{str2} are the same,
disregarding case of alphabetic characters, and 0 otherwise.

If either @var{str1} or @var{str2} is a cell array of strings, then an array
of the same size is returned, containing the values described above for
every member of the cell array.  The other argument may also be a cell
array of strings (of the same size or with only one element), char matrix
or character string.

@strong{Caution:} For compatibility with @sc{matlab}, Octave's strcmp
function returns 1 if the character strings are equal, and 0 otherwise.
This is just the opposite of the corresponding C library function.

@strong{Caution:} National alphabets are not supported.
@seealso{strcmp, strncmp, strncmpi}
@end deftypefn */)
{
  if (args.length () != 2)
    print_usage ();

  return ovl (do_strcmp_fcn (args(0), args(1), 0, "strcmpi",
                             strcmpi_ignore_n, strcmpi_ignore_n));
}

/*
%!assert (strcmpi ("abc123", "ABC123"), true)
*/

DEFUNX ("strncmpi", Fstrncmpi, args, ,
        doc: /* -*- texinfo -*-
@deftypefn {} {@var{tf} =} strncmpi (@var{str1}, @var{str2}, @var{n})
Return 1 if the first @var{n} character of @var{s1} and @var{s2} are the
same, disregarding case of alphabetic characters, and 0 otherwise.

If either @var{str1} or @var{str2} is a cell array of strings, then an array
of the same size is returned, containing the values described above for
every member of the cell array.  The other argument may also be a cell
array of strings (of the same size or with only one element), char matrix
or character string.

@strong{Caution:} For compatibility with @sc{matlab}, Octave's strncmpi
function returns 1 if the character strings are equal, and 0 otherwise.
This is just the opposite of the corresponding C library function.

@strong{Caution:} National alphabets are not supported.
@seealso{strncmp, strcmp, strcmpi}
@end deftypefn */)
{
  if (args.length () != 3)
    print_usage ();

  octave_idx_type n = args(2).idx_type_value ();

  if (n > 0)
    return ovl (do_strcmp_fcn (args(0), args(1), n, "strncmpi",
                               string::strncmpi,
                               string::strncmpi));
  else
    error ("strncmpi: N must be greater than 0");
}

/*
%!assert (strncmpi ("abc123", "ABC456", 3), true)

%!assert <*54373> (strncmpi ("abc", "abC", 100))
*/

DEFUN (str2double, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{d} =} str2double (@var{str})
Convert a string to a real or complex number.

The string must be in one of the following formats where a and b are real
numbers and the complex unit is @qcode{'i'} or @qcode{'j'}:

@itemize
@item a + bi

@item a + b*i

@item a + i*b

@item bi + a

@item b*i + a

@item i*b + a
@end itemize

If present, a and/or b are of the form @nospell{[+-]d[,.]d[[eE][+-]d]} where
the brackets indicate optional arguments and @qcode{'d'} indicates zero or
more digits.  The special input values @code{Inf}, @code{NaN}, and @code{NA}
are also accepted.

@var{str} may be a character string, character matrix, or cell array.  For
character arrays the conversion is repeated for every row, and a double or
complex array is returned.  Empty rows in @var{s} are deleted and not
returned in the numeric array.  For cell arrays each character string
element is processed and a double or complex array of the same dimensions as
@var{str} is returned.

For unconvertible scalar or character string input @code{str2double} returns
a NaN@.  Similarly, for character array input @code{str2double} returns a
NaN for any row of @var{s} that could not be converted.  For a cell array,
@code{str2double} returns a NaN for any element of @var{s} for which
conversion fails.  Note that numeric elements in a mixed string/numeric
cell array are not strings and the conversion will fail for these elements
and return NaN.

Programming Note: @code{str2double} can replace @code{str2num}, is more
efficient, and avoids the security risk of using @code{eval} on unknown data.
@seealso{str2num}
@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  octave_value retval;

  if (args(0).is_string ())
    {
      if (args(0).rows () == 0 || args(0).columns () == 0)
        retval = Matrix (1, 1, numeric_limits<double>::NaN ());
      else if (args(0).rows () == 1 && args(0).ndims () == 2)
        retval = string::str2double (args(0).string_value ());
      else
        {
          const string_vector sv = args(0).string_vector_value ();

          retval = sv.map<Complex> (string::str2double);
        }
    }
  else if (args(0).iscell ())
    {
      const Cell cell = args(0).cell_value ();

      ComplexNDArray output (cell.dims (), numeric_limits<double>::NaN ());

      for (octave_idx_type i = 0; i < cell.numel (); i++)
        {
          if (cell(i).is_string ())
            output(i) = string::str2double (cell(i).string_value ());
        }
      retval = output;
    }
  else
    retval = Matrix (1, 1, numeric_limits<double>::NaN ());

  return retval;
}

/*
%!assert (str2double ("1"), 1)
%!assert (str2double ("-.1e-5"), -1e-6)
%!testif ; ! ismac ()
%! assert (str2double (char ("1", "2 3", "4i")), [1; NaN; 4i]);
%!test <47413>
%! ## Same test code as above, but intended only for test statistics on Mac.
%! if (! ismac ()), return; endif
%! assert (str2double (char ("1", "2 3", "4i")), [1; NaN; 4i]);
%!assert (str2double ("1,222.5"), 1222.5)
%!assert (str2double ("i"), i)
%!assert (str2double ("2j"), 2i)
%!assert (str2double ("2 + j"), 2+j)
%!assert (str2double ("i*2 + 3"), 3+2i)
%!assert (str2double (".5*i + 3.5"), 3.5+0.5i)
%!assert (str2double ("1e-3 + i*.25"), 1e-3 + 0.25i)
%!assert (str2double (char ("2 + j","1.25e-3","-05")), [2+i; 1.25e-3; -5])
%!assert (str2double ({"2 + j","1.25e-3","-05"}), [2+i, 1.25e-3, -5])
%!assert (str2double (1), NaN)
%!assert (str2double ("1 2 3 4"), NaN)
%!assert (str2double ("Hello World"), NaN)
%!assert (str2double ("NaN"), NaN)
%!assert (str2double ("NA"), NA)
%!assert (str2double ("Inf"), Inf)
%!assert (str2double ("iNF"), Inf)
%!assert (str2double ("-Inf"), -Inf)
%!assert (str2double ("Inf*i"), complex (0, Inf))
%!assert (str2double ("iNF*i"), complex (0, Inf))
%!assert (str2double ("NaN + Inf*i"), complex (NaN, Inf))
%!assert (str2double ("Inf - Inf*i"), complex (Inf, -Inf))
%!assert (str2double ("-i*NaN - Inf"), complex (-Inf, -NaN))
%!testif ; ! ismac ()
%! assert (str2double ({"abc", "4i"}), [NaN + 0i, 4i]);
%!test <47413>
%! if (! ismac ()), return; endif
%! assert (str2double ({"abc", "4i"}), [NaN + 0i, 4i]);
%!testif ; ! ismac ()
%! assert (str2double ({2, "4i"}), [NaN + 0i, 4i])
%!test <47413>
%! if (! ismac ()), return; endif
%! assert (str2double ({2, "4i"}), [NaN + 0i, 4i])
%!assert (str2double (zeros (3,1,2)), NaN)
%!assert (str2double (''), NaN)
%!assert (str2double ([]), NaN)
%!assert (str2double (char (zeros (3,0))), NaN)
*/

DEFUN (__native2unicode__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{utf8_str} =} __native2unicode__ (@var{native_bytes}, @var{codepage})
Convert byte stream @var{native_bytes} to UTF-8 using @var{codepage}.

@seealso{native2unicode, __unicode2native__}
@end deftypefn */)
{
  if (args(0).is_string ())
    return ovl (args(0));

  std::string tmp = args(1).string_value ();
  const char *codepage
    = (tmp.empty () ? octave_locale_charset_wrapper () : tmp.c_str ());

  charNDArray native_bytes = args(0).char_array_value ();

  const char *src = native_bytes.data ();
  std::size_t srclen = native_bytes.numel ();

  std::size_t length;
  uint8_t *utf8_str = nullptr;

  utf8_str = octave_u8_conv_from_encoding (codepage, src, srclen, &length);

  if (! utf8_str)
    {
      if (errno == ENOSYS)
        error ("native2unicode: iconv() is not supported.  Installing GNU "
               "libiconv and then re-compiling Octave could fix this.");
      else
        error ("native2unicode: converting from codepage '%s' to UTF-8: %s",
               codepage, std::strerror (errno));
    }

  unwind_action free_utf8_str ([=] () { ::free (utf8_str); });

  octave_idx_type len = length;

  charNDArray retval (dim_vector (1, len));

  for (octave_idx_type i = 0; i < len; i++)
    retval.xelem (i) = utf8_str[i];

  return ovl (retval);
}

DEFUN (__unicode2native__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{native_bytes} =} __unicode2native__ (@var{utf8_str}, @var{codepage})
Convert UTF-8 string @var{utf8_str} to byte stream @var{native_bytes} using
@var{codepage}.

@seealso{unicode2native, __native2unicode__}
@end deftypefn */)
{
  std::string tmp = args(1).string_value ();
  const char *codepage
    = (tmp.empty () ? octave_locale_charset_wrapper () : tmp.c_str ());

  charNDArray utf8_str = args(0).char_array_value ();

  const uint8_t *src = reinterpret_cast<const uint8_t *> (utf8_str.data ());
  std::size_t srclen = utf8_str.numel ();

  std::size_t length;
  char *native_bytes = nullptr;

  native_bytes = octave_u8_conv_to_encoding (codepage, src, srclen, &length);

  if (! native_bytes)
    {
      if (errno == ENOSYS)
        error ("unicode2native: iconv() is not supported.  Installing GNU "
               "libiconv and then re-compiling Octave could fix this.");
      else
        error ("unicode2native: converting from UTF-8 to codepage '%s': %s",
               codepage, std::strerror (errno));
    }

  unwind_action free_native_bytes ([=] () { ::free (native_bytes); });

  octave_idx_type len = length;

  uint8NDArray retval (dim_vector (1, len));

  for (octave_idx_type i = 0; i < len; i++)
    retval.xelem (i) = native_bytes[i];

  return ovl (retval);
}

DEFUN (__locale_charset__, , ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{charset} =} __locale_charset__ ()
Return the identifier for the charset used if the encoding is set to
@qcode{"locale"}.
@end deftypefn */)
{
  const char *charset = octave_locale_charset_wrapper ();
  std::string charset_str (charset);
  return ovl (charset_str);
}

DEFUN (unicode_idx, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{idx} =} unicode_idx (@var{str})
Return an array with the indices for each UTF-8 encoded character in @var{str}.

@example
@group
unicode_idx ("aäbc")
     @result{} [1, 2, 2, 3, 4]
@end group
@end example

@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  charNDArray str = args(0).xchar_array_value ("STR must be a string");
  Array<octave_idx_type> p (dim_vector (str.ndims (), 1));
  charNDArray str_p;
  if (str.ndims () > 1)
    {
      for (octave_idx_type i=0; i < str.ndims (); i++)
        p(i) = i;
      p(0) = 1;
      p(1) = 0;
      str_p = str.permute (p);
    }

  const uint8_t *src = reinterpret_cast<const uint8_t *> (str_p.data ());
  octave_idx_type srclen = str.numel ();

  NDArray idx (str_p.dims ());

  octave_idx_type u8_char_num = 1;
  for (octave_idx_type i = 0; i < srclen; u8_char_num++)
    {
      int mblen = octave_u8_strmblen_wrapper (src + i);
      if (mblen < 1)
        mblen = 1;
      for (octave_idx_type j = 0; j < mblen; j++)
        idx(i+j) = u8_char_num;
      i += mblen;
    }

  return ovl (str.ndims () > 1 ? idx.permute (p, true) : idx);
}

/*
%!assert (unicode_idx (["aäou"; "Ä∞"]), [1 2 2 3 4; 5 5 6 6 6])
*/

DEFUN (__unicode_length__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{len} =} __unicode_length__ (@var{str})
Return number of Unicode code points in @var{str}.

The input @var{str} must be a UTF-8 encoded character vector or cell string.

@example
@group
length ("aäbc")
     @result{} 5
__unicode_length__ ("aäbc")
     @result{} 4
@end group
@end example

@end deftypefn */)
{
  if (args.length () != 1)
    print_usage ();

  bool arg_char = args(0).is_char_matrix ();

  if (! arg_char && ! args(0).iscellstr ())
    error ("STR must be a character array or cell string.");

  octave_value_list retval;

  if (arg_char)
    {
      charNDArray str = args(0).char_array_value ();
      Array<octave_idx_type> p (dim_vector (str.ndims (), 1));
      if (str.ndims () > 1)
        {
          for (octave_idx_type i=0; i < str.ndims (); i++)
            p(i) = i;
          p(0) = 1;
          p(1) = 0;
          str = str.permute (p);
        }

      const uint8_t *src = reinterpret_cast<const uint8_t *> (str.data ());
      octave_idx_type mbsnlen = octave_u8_mbsnlen_wrapper (src, str.numel ());

      retval = ovl (mbsnlen);
    }
  else
    {
      const Array<std::string> cellstr = args(0).cellstr_value ();
      NDArray output (args(0).dims (), false);
      for (octave_idx_type i = 0; i < cellstr.numel (); i++)
        {
          const uint8_t *src
            = reinterpret_cast<const uint8_t *> (cellstr(i).c_str ());
          output(i) = octave_u8_mbsnlen_wrapper (src, cellstr(i).size ());
        }

      retval = ovl (output);
    }

  return retval;
}

/*
%!assert (__unicode_length__ (""), 0)
%!assert (__unicode_length__ ("aäbc"), 4)
%!assert (__unicode_length__ (["aä"; "öo"]), 4)
%!assert (__unicode_length__ ({"aäbc", "abc"}), [4, 3])
*/

DEFUN (__u8_validate__, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{out_str} =} __u8_validate__ (in_str, mode)
Return string with valid UTF-8.

On encountering invalid UTF-8 in @var{in_str}, the bytes are either replaced by
the replacement character @qcode{"�"} (if @var{mode} is omitted or is the
string @qcode{"replace"}) or interpreted as the Unicode code points
U+0080–U+00FF with the same value as the byte (if @var{mode} is the string
@qcode{"unicode"}), thus interpreting the bytes according to ISO-8859-1.
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 1 || nargin > 2)
    print_usage ();

  // Input check
  std::string in_str =
    args(0).xstring_value ("__u8_validate__: IN_STR must be a string");

  std::string mode = "replace";
  if (nargin == 2)
    mode = args(1).xstring_value ("__u8_validate__: MODE must be a string");

  string::u8_fallback_type fb_type;
  if (mode == "replace")
    fb_type = string::U8_REPLACEMENT_CHAR;
  else if (mode == "unicode")
    fb_type = string::U8_ISO_8859_1;
  else
    error (R"(__u8_validate__: MODE must be either "replace" or "unicode")");

  string::u8_validate ("__u8_validate__", in_str, fb_type);

  return ovl (in_str);
}

DEFUN (newline, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{c} =} newline
Return the character corresponding to a newline.

This is equivalent to @qcode{"@backslashchar{}n"}.

Example Code

@example
@group
joined_string = [newline "line1" newline "line2"]
@result{}
line1
line2
@end group
@end example

@seealso{strcat, strjoin, strsplit}
@end deftypefn */)
{
  if (args.length () != 0)
    print_usage ();

  static octave_value_list retval = ovl ("\n");

  return retval;
}

/*
%!assert (newline (), "\n")

%!error newline (1)
## FIXME: The next error() test requires a semicolon at EOL until
##        bug #59265 is resolved.
%!error [a, b] = newline ();
*/

DEFUN (list_in_columns, args, ,
       doc: /* -*- texinfo -*-
@deftypefn {} {@var{str} =} list_in_columns (@var{arg}, @var{width}, @var{prefix})
Return a string containing the elements of @var{arg} listed in columns with
an overall maximum width of @var{width} and optional prefix @var{prefix}.

The argument @var{arg} must be a cell array of character strings or a
character array.

If @var{width} is not specified or is an empty matrix, or less than or equal
to zero, the width of the terminal screen is used.  Newline characters are
used to break the lines in the output string.  For example:
@c Set example in small font to prevent overfull line

@smallexample
@group
list_in_columns (@{"abc", "def", "ghijkl", "mnop", "qrs", "tuv"@}, 20)
     @result{} abc     mnop
        def     qrs
        ghijkl  tuv

whos ans
     @result{}
     Variables in the current scope:

       Attr Name        Size                     Bytes  Class
       ==== ====        ====                     =====  =====
            ans         1x37                        37  char

     Total is 37 elements using 37 bytes
@end group
@end smallexample

@seealso{terminal_size}
@end deftypefn */)
{
  int nargin = args.length ();

  if (nargin < 1 || nargin > 3)
    print_usage ();

  string_vector s = args(0).xstring_vector_value ("list_in_columns: ARG must be a cellstr or char array");

  int width = -1;

  if (nargin > 1 && ! args(1).isempty ())
    width = args(1).xint_value ("list_in_columns: WIDTH must be an integer");

  std::string prefix;

  if (nargin > 2)
    prefix = args(2).xstring_value ("list_in_columns: PREFIX must be a string");

  std::ostringstream buf;

  s.list_in_columns (buf, width, prefix);

  return ovl (buf.str ());
}

/*
%!test
%! input  = {"abc", "def", "ghijkl", "mnop", "qrs", "tuv"};
%! result = "abc     mnop\ndef     qrs\nghijkl  tuv\n";
%! assert (list_in_columns (input, 20), result);
%!test
%! input  = char ("abc", "def", "ghijkl", "mnop", "qrs", "tuv");
%! result = "abc     mnop  \ndef     qrs   \nghijkl  tuv   \n";
%! assert (list_in_columns (input, 20), result);
%!test
%! input  = char ("abc", "def", "ghijkl", "mnop", "qrs", "tuv");
%! result = "  abc     mnop  \n  def     qrs   \n  ghijkl  tuv   \n";
%! assert (list_in_columns (input, 20, "  "), result);

%!error list_in_columns ()
%!error list_in_columns (["abc", "def"], 20, 2)
%!error list_in_columns (["abc", "def"], 20, "  ", 3)
%!error <list_in_columns: WIDTH must be an integer> list_in_columns (["abc", "def"], "a")
*/

OCTAVE_END_NAMESPACE(octave)