/* split.cc - split a line into words
 * Copyright 2007 Bas Wijnen <wijnen@debian.org>
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "split.hh"
#include "error.hh"
#include <iterator>

namespace
{
  bool get_digit (char c, unsigned &d)
  {
    if (c >= '0' && c <= '9')
      {
	d = c - '0';
	return true;
      }
    if (c >= 'a' && c <= 'f')
      {
	d = c - 'a' + 10;
	return true;
      }
    if (c >= 'A' && c <= 'F')
      {
	d = c - 'A' + 10;
	return true;
      }
    return false;
  }
}

namespace shevek
{
  void split::load (std::string const &str, bool allow_empty, std::string const &delimiters)
  {
    std::string::size_type p = 0;
    bool have_quote = false;
    while (true)
      {
	if (!allow_empty)
	  p = str.find_first_not_of (delimiters, p);
	if (p == std::string::npos || p >= str.size ())
	  break;
	push_back (std::string ());
	char quote = 0;
	for (; true; ++p)
	  {
	    if (p == std::string::npos || p >= str.size ())
	      break;
	    if (have_quote)
	      {
		if (str[p] == quote)
		  {
		    have_quote = false;
		    continue;
		  }
		back () += str[p];
		continue;
	      }
	    if (str[p] == '"' || str[p] == '\'')
	      {
		quote = str[p];
		have_quote = true;
		continue;
	      }
	    if (delimiters.find (str[p]) != std::string::npos)
	      {
		++p;
		break;
	      }
	    if (str[p] == '\\')
	      {
		++p;
		if (p == str.size ())
		  {
		    shevek_error ("backslash at end of line");
		    return;
		  }
		if (str[p] >= '0' && str[p] < '8')
		  {
		    unsigned d[3];
		    d[0] = str[p] - '0';
		    if (str.size () > p + 1)
		      d[1] = str[p + 1] - '0';
		    else
		      d[1] = 9;
		    if (d[1] < 8 && str.size () > p + 2)
		      d[2] = str[p + 2] - '0';
		    else
		      d[2] = 9;
		    char result = 0;
		    for (unsigned i = 0; i < 3; ++i)
		      {
			if (d[i] >= 8)
			  break;
			++p;
			result <<= 3;
			result |= d[i];
		      }
		    --p;
		    back () += result;
		    continue;
		  }
		switch (str[p])
		  {
		  case 'x':
		    {
		      ++p;
		      unsigned d[2];
		      if (str.size () <= p)
			{
			  shevek_error ("\\x at end of line");
			  return;
			}
		      if (!get_digit (str[p], d[0]))
			{
			  shevek_error ("no hex digit after \\x");
			  return;
			}
		      if (str.size () == p + 1
			  || !get_digit (str[p + 1], d[1]))
			{
			  back () += (char)d[0];
			  continue;
			}
		      ++p;
		      back () += (char)((d[0] << 4) + d[1]);
		      continue;
		    }
		  case 'n':
		    back () += '\n';
		    continue;
		  case 'r':
		    back () += '\r';
		    continue;
		  case 't':
		    back () += '\t';
		    continue;
		  case 'a':
		    back () += '\a';
		    continue;
		  case 'v':
		    back () += '\v';
		    continue;
		  case 'f':
		    back () += '\f';
		    continue;
		  }
		// fall through
	      }
	    back () += str[p];
	  }
      }
  }
  split split::sub (unsigned from) const
  {
    split ret;
    if (from < size ())
      {
	std::copy (begin () + from, end (),
		   std::back_inserter (ret));
      }
    return ret;
  }
}
