## Copyright (C) 2009-2015 Eric Chassande-Mottin, CNRS (France)
## Parts Copyright (C) 2012-2015 Philip Nienhuis
##
## 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/>.

## -*- texinfo -*-
## @deftypefn  {} {[@var{a}, @dots{}] =} strread (@var{str})
## @deftypefnx {} {[@var{a}, @dots{}] =} strread (@var{str}, @var{format})
## @deftypefnx {} {[@var{a}, @dots{}] =} strread (@var{str}, @var{format}, @var{format_repeat})
## @deftypefnx {} {[@var{a}, @dots{}] =} strread (@var{str}, @var{format}, @var{prop1}, @var{value1}, @dots{})
## @deftypefnx {} {[@var{a}, @dots{}] =} strread (@var{str}, @var{format}, @var{format_repeat}, @var{prop1}, @var{value1}, @dots{})
## Read data from a string.
##
## @seealso{textscan, textread, load, dlmread, fscanf}
## @end deftypefn

function varargout = ystrread (str, format = "%f", varargin)

  ## Check input
  if (nargin < 1)
    print_usage ();
  endif

  if (isempty (str))
    ## Return empty args (no match), rather than raising an error
    varargout = cell (1, nargout);
    return;
  endif

  if (isempty (format))
    format = "%f";
  endif

  if (! ischar (str) || ! ischar (format))
    error ("strread: STR and FORMAT arguments must be strings");
  endif

  if (strcmp (typeinfo (format), "sq_string"))
    format = do_string_escapes (format);
  endif

  ## Parse format string to compare number of conversion fields and nargout
  nfields = numel (regexp (format, '(%(\d*|\d*\.\d*)?[nfduscq]|%\[)', "match"));
  ## If str only has numeric fields, a (default) format ("%f") will do.
  ## Otherwise:
  if (! nfields)
    error ("strread.m: no valid format conversion specifiers found\n");
  elseif ((max (nargout, 1) != nfields) && ! strcmp (format, "%f"))
    error ("strread: the number of output variables must match that specified by FORMAT");
  endif

  ## Check for format string repeat count
  format_repeat_count = -1;
  if (nargin > 2 && isnumeric (varargin{1}))
    format_repeat_count = 1;
  else
    format_repeat_count = 0;
  endif

  ## Parse options.  First initialize defaults
  comment_flag = false;
  delimiter_str = "";
  empty_str = "";
  eol_char = "";
  err_action = 0;
  mult_dlms_s1 = false;
  numeric_fill_value = NaN;
  white_spaces = " \b\r\n\t";
  ## Process commentstyle if found
  for n = format_repeat_count+1:2:length (varargin)
    switch (lower (varargin{n}))
      case "commentstyle"
        comment_flag = true;
        switch (lower (varargin{n+1}))
          case "c"
            [comment_start, comment_end] = deal ("/*", "*/");
          case "c++"
            [comment_start, comment_end] = deal ("//", "");
          case "shell"
            [comment_start, comment_end] = deal ("#" , "");
          case "matlab"
            [comment_start, comment_end] = deal ("%" , "");
          otherwise
            if (ischar (varargin{n+1})
                || (numel (varargin{n+1}) == 1 && iscellstr (varargin{n+1})))
              [comment_start, comment_end] = deal (char (varargin{n+1}), "");
            elseif (iscellstr (varargin{n+1}) && numel (varargin{n+1}) == 2)
              [comment_start, comment_end] = deal (varargin{n+1}{:});
            else
              ## FIXME: A user may have numeric values specified: {'//', 7}
              ##        this will lead to an error in the warning message
              error ("strread: unknown or unrecognized comment style '%s'",
                      varargin{n+1});
            endif

        endswitch

      varargin{n+1} = {comment_start, comment_end};
      if (isempty (comment_end))
        varargin{n+1}(2) = [];
      endif
    endswitch
  endfor

  ## Call ytextscan & postprocess its output
  C = ytextscan (str, format, varargin{:});
  for ii=1:numel (C);
    varargout{ii} = C{ii};
  endfor

endfunction

%!test
%! [a, b] = ystrread ("1 2", "%f%f");
%! assert (a, 1);
%! assert (b, 2);

%!test
%! str = "";
%! a = rand (10, 1);
%! b = char (randi ([65, 85], 10, 1));
%! for k = 1:10
%!   str = sprintf ("%s %.6f %s\n", str, a(k), b(k));
%! endfor
%! [aa, bb] = ystrread (str, "%f %s");
%! assert (aa, a, 1e-6);
%! assert (bb, cellstr (b));

%!test
%! str = "";
%! a = rand (10, 1);
%! b = char (randi ([65, 85], 10, 1));
%! for k = 1:10
%!   str = sprintf ("%s %.6f %s\n", str, a(k), b(k));
%! endfor
%! aa = ystrread (str, "%f %*s");
%! assert (aa, a, 1e-6);

%!test
%! str = sprintf ("/* this is\nacomment*/ 1 2 3");
%! a = ystrread (str, "%f", "commentstyle", "c");
%! assert (a, [1; 2; 3]);

%!test
%! str = "# comment\n# comment\n1 2 3";
%! [a, b] = ystrread (str, "%n %s", "commentstyle", "shell", "endofline", "\n");
%! assert (a, [1; 3]);
#################################%! assert (b, {"2"});
%! assert (b, {"2"; ""});

%!test
%! assert (ystrread ("Hello World! // this is comment", "%s",...
%! "commentstyle", "c++"), ...
%! {"Hello"; "World!"});
%! assert (ystrread ("Hello World! % this is comment", "%s",...
%! "commentstyle", "matlab"), ...
%! {"Hello"; "World!"});
%! assert (ystrread ("Hello World! # this is comment", "%s",...
%! "commentstyle", "shell"), ...
%! {"Hello"; "World!"});

%!test
%! str = sprintf ("Tom 100 miles/hr\nDick 90 miles/hr\nHarry 80 miles/hr");
%! fmt = "%s %f miles/hr";
%! c = cell (1, 2);
%! [c{:}] = ystrread (str, fmt);
%! assert (c{1}, {"Tom"; "Dick"; "Harry"})
%! assert (c{2}, [100; 90; 80])

%!test
%! a = ystrread ("a b c, d e, , f", "%s", "delimiter", ",");
%! assert (a, {"a b c"; "d e"; ""; "f"});

%! ## Format repeat counters w & w/o trailing EOL even within partly read files
%!test
%! [a, b] = ystrread ("10 a 20 b\n 30 c 40", "%d %s", 4);
%! assert (a, int32 ([10; 20; 30; 40]));
%! assert (b, {"a"; "b"; "c"});
%! [a, b] = ystrread ("10 a 20 b\n 30 c 40\n", "%d %s", 4);
%! assert (a, int32 ([10; 20; 30; 40]));
%! assert (b, {"a"; "b"; "c"; ""});
%! [a, b] = ystrread ("10 a 20 b\n 30 c 40", "%d %s", 1);
%! assert (a, int32 (10));
%! assert (b, {"a"});

%!test
%! ## Bug #33536
%! [a, b, c] = ystrread ("1,,2", "%s%s%s", "delimiter", ",");
%! assert (a{1}, "1");
%! assert (b{1}, "");
%! assert (c{1}, "2");

%!test
%! ## Bug #33536
%! a = ystrread ("[SomeText]", "[%s", "delimiter", "]");
%! assert (a{1}, "SomeText");

%!test
%! dat = "Data file.\r\n=  =  =  =  =\r\nCOMPANY    : <Company name>\r\n";
%! a = ystrread (dat, "%s", "delimiter", "\n", "whitespace", "", "endofline", "\r\n");
%! assert (a{2}, "=  =  =  =  =");
%! assert (double (a{3}(end-5:end)), [32 110 97 109 101 62]);

%!test
%! [a, b, c, d] = ystrread ("1,2,3,,5,6", "%d%f%d%f", "delimiter", ",");
%! assert (c, int32 (3));
%! assert (d, NaN);

%!test
%! [a, b, c, d] = ystrread ("1,2,3,,5,6\n", "%d%d%f%d", "delimiter", ",");
%! assert (c, [3; NaN]);
%! assert (d, int32 ([0; 0]));

%!test
%! ## Default format (= %f)
%! [a, b, c] = ystrread ("0.12 0.234 0.3567");
%! assert (a, 0.12);
%! assert (b, 0.234);
%! assert (c, 0.3567);

%!test
%! [a, b] = ystrread ("0.41 8.24 3.57 6.24 9.27", "%f%f", 2, "delimiter", " ");
%! assert (a, [0.41; 3.57]);

%!test
%! ## TreatAsEmpty
%! [a, b, c, d] = ystrread ("1,2,3,NN,5,6\n", "%d%d%d%f", "delimiter", ",", "TreatAsEmpty", "NN");
%! assert (c, int32 ([3; 0]));
%! assert (d, [NaN; NaN]);

%!test
%! ## No delimiters at all besides EOL.  Plain reading numbers & strings
%! str = "Text1Text2Text\nText398Text4Text\nText57Text";
%! [a, b] = ystrread (str, "Text%dText%1sText");
%! assert (a, int32 ([1; 398; 57]));
%! assert (b(1:2), {"2"; "4"});
%! assert (isempty (b{3}), true);

## MultipleDelimsAsOne
%!test
%! str = "11, 12, 13,, 15\n21,, 23, 24, 25\n,, 33, 34, 35";
%! [a b c d] = ystrread (str, "%f %f %f %f", "delimiter", ",", "multipledelimsasone", 1, "endofline", "\n");
%! assert (a', [11, 21, NaN]);
%! assert (b', [12, 23, 33]);
%! assert (c', [13, 24, 34]);
%! assert (d', [15, 25, 35]);

## Bug #44750
%!test
%! assert (ystrread ('/home/foo/','%s','delimiter','/','MultipleDelimsAsOne',1), ...
%!         {"home"; "foo"});

## delimiter as sq_string and dq_string
%!test
%! assert (ystrread ("1\n2\n3", "%d", "delimiter", "\n"),
%!         ystrread ("1\n2\n3", "%d", "delimiter", '\n'))

## whitespace as sq_string and dq_string
%!test
%! assert (ystrread ("1\b2\r3\b4\t5", "%d", "whitespace", "\b\r\n\t"),
%!         ystrread ("1\b2\r3\b4\t5", "%d", "whitespace", '\b\r\n\t'))

%!test
%! str =  "0.31 0.86 0.94\n 0.60 0.72 0.87";
%! fmt = "%f %f %f";
%! args = {"delimiter", " ", "endofline", "\n", "whitespace", " "};
%! [a, b, c] = ystrread (str, fmt, args{:});
%! assert (a, [0.31; 0.60], 0.01)
%! assert (b, [0.86; 0.72], 0.01)
%! assert (c, [0.94; 0.87], 0.01)

%!test
%! str =  "0.31,0.86,0.94\n0.60,0.72,0.87";
%! fmt = "%f %f %f";
%! args = {"delimiter", ",", "endofline", "\n", "whitespace", " "};
%! [a, b, c] = ystrread (str, fmt, args{:});
%! assert (a, [0.31; 0.60], 0.01)
%! assert (b, [0.86; 0.72], 0.01)
%! assert (c, [0.94; 0.87], 0.01)

%!test
%! str =  "0.31 0.86 0.94\n 0.60 0.72 0.87";
%! fmt = "%f %f %f";
%! args = {"delimiter", ",", "endofline", "\n", "whitespace", " "};
%! [a, b, c] = ystrread (str, fmt, args{:});
%! assert (a, [0.31; 0.60], 0.01)
%! assert (b, [0.86; 0.72], 0.01)
%! assert (c, [0.94; 0.87], 0.01)

%!test
%! str =  "0.31, 0.86, 0.94\n 0.60, 0.72, 0.87";
%! fmt = "%f %f %f";
%! args = {"delimiter", ",", "endofline", "\n", "whitespace", " "};
%! [a, b, c] = ystrread (str, fmt, args{:});
%! assert (a, [0.31; 0.60], 0.01)
%! assert (b, [0.86; 0.72], 0.01)
%! assert (c, [0.94; 0.87], 0.01)

%!test
%! [a, b] = ystrread (["Empty 1" char(10)], "Empty%s %f");
%! assert (a{1}, '1');
%! assert (b, NaN);

%!test
%! [a, b] = ystrread (["Empty" char(10)], "Empty%f %f");
%! assert (a, NaN);
%! assert (b, NaN);

%!test
%! ## Bug #35999
%! [a, b, c] = ystrread ("", "%f");
%! assert (isempty (a));
%! assert (isempty (b));
%! assert (isempty (c));

%!test
%! ## bug #37023
%! [a, b] = ystrread (" 1. 1 \n  2 3 \n", "%f %f", "endofline", "\n");
%! assert (a, [1; 2], 1e-15);
%! assert (b, [1; 3], 1e-15);

## Test for no output arg (interactive use)
%!test
%! assert (ystrread (",2,,4\n5,,7,", "", "delimiter", ","), [NaN; 2; NaN; 4; 5; NaN; 7]);

## Test #1 bug #42609
%!test
%! [a, b, c] = ystrread ("1 2 3\n4 5 6\n7 8 9\n", "%f %f %f\n");
%! assert (a, [1; 4; 7]);
%! assert (b, [2; 5; 8]);
%! assert (c, [3; 6; 9]);

## Test #2 bug #42609
%!test
%! [a, b, c] = ystrread ("1 2\n3\n4 5\n6\n7 8\n9\n", "%f %f\n%f");
%! assert (a, [1;4;7]);
%! assert (b, [2; 5; 8]);
%! assert (c, [3; 6; 9]);

## Test #3 bug #42609
%!test
%! [a, b, c] = ystrread ("1 2 3\n4 5 6\n7 8 9\n", '%f %f %f\n');
%! assert (a, [1; 4; 7]);
%! assert (b, [2; 5; 8]);
%! assert (c, [3; 6; 9]);

## Test #4 bug #42609
%!test
%! [a, b, c] = ystrread ("1 2\n3\n4 5\n6\n7 8\n9\n", '%f %f\n%f');
%! assert (a, [1;4;7]);
%! assert (b, [2; 5; 8]);
%! assert (c, [3; 6; 9]);

## Unsupported format specifiers
%!test
%!error <format specifiers are not supported> ystrread ("a", "%c")
%!error <format specifiers are not supported> ystrread ("a", "%*c %d")
%!error <format specifiers are not supported> ystrread ("a", "%q")
%!error <format specifiers are not supported> ystrread ("a", "%*q %d")
%!error <format specifiers are not supported> ystrread ("a", "%[a]")
%!error <format specifiers are not supported> ystrread ("a", "%*[a] %d")
%!error <format specifiers are not supported> ystrread ("a", "%[^a]")
%!error <format specifiers are not supported> ystrread ("a", "%*[^a] %d")
%!error <format specifiers are not supported> ystrread ("a", "%d8")
%!error <format specifiers are not supported> ystrread ("a", "%*d8 %s")
%!error <format specifiers are not supported> ystrread ("a", "%f64")
%!error <format specifiers are not supported> ystrread ("a", "%*f64 %s")
%!error <format specifiers are not supported> ystrread ("a", "%u32")
%!error <format specifiers are not supported> ystrread ("a", "%*u32 %d")

## Illegal format specifiers
%!test
%!error <no valid format conversion specifiers> ystrread ("1.0", "%z");

## Test for false positives in check for non-supported format specifiers
%!test
%! assert (ystrread ("Total: 32.5 % (of cm values)","Total: %f % (of cm values)"), 32.5, 1e-5);

## Test various forms of string format specifiers (bug #45712)
%!test
%! str = "14 :1 z:2 z:3 z:5 z:11";
%! [a, b, c, d] = ystrread (str, "%f %s %*s %3s %*3s %f", "delimiter", ":");
%! assert ({a, b, c, d}, {14, {"1 z"}, {"3 z"}, 11});

## Allow cuddling %sliteral but warn it is ambiguous
%!test
%! [a, b] = ystrread ("abcxyz51\nxyz83\n##xyz101", "%s xyz %d");
%! assert (a([1 3]), {"abc"; "##"});
%! assert (isempty (a{2}), true);
%! assert (b, int32([51; 83; 101]));

