/* $Id: parseopt.C,v 1.10 2001/01/13 19:46:10 dm Exp $ */

/*
 *
 * Copyright (C) 1998 David Mazieres (dm@uun.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 2, 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "amisc.h"
#include "parseopt.h"

char *parseargs::errorbuf = "";

static inline int
isspc (char c)
{
  return c == ' ' || c == '\t' || c == '\n';
}
static inline int
isspcnnl (char c)
{
  return c == ' ' || c == '\t';
}

void
parseargs::skipblanks ()
{
  bool bol = true;
  while (p < lim) {
    if (bol && *p == '#') {
      while (p < lim && *p != '\n')
	p++;
      if (p < lim) {
	lineno++;
	p++;
      }
      continue;
    }
    if (isspcnnl (*p)) {
      bol = false;
      p++;
    }
    else if (*p == '\n') {
      lineno++;
      p++;
      bol = true;
    }
    else if (p[0] == '\\' && p[1] == '\n') {
      p += 2;
      lineno++;
      bol = false;
    }
    else
      return;
  }
}
void
parseargs::skiplwsp ()
{
  for (;;) {
    if (isspcnnl (*p))
      p++;
    else if (p[0] == '\\' && p[1] == '\n') {
      p += 2;
      lineno++;
    }
    else
      return;
  }
}

str
parseargs::getarg ()
{
  skiplwsp ();
  if (p >= lim || *p == '\n')
    return NULL;

  bool q = false;
  vec<char> arg;
  for (;;) {
    if (*p == '\\') {
      if (p + 1 >= lim) {
	error ("invalid '\\' before end of file");
	return NULL;
      }
      else if (p[1] == '\n')
	skiplwsp ();
      else {
	arg.push_back (p[1]);
	p += 2;
      }
      continue;
    }
    if (p >= lim) {
      if (q)
	error ("closing '\"' missing");
      return str (arg.base (), arg.size ());
    }
    if (*p == '\"')
      q = !q;
    else if (q || !isspc (*p))
      arg.push_back (*p);
    else
      return str (arg.base (), arg.size ());
    p++;
  }
  return NULL;			// XXX - egcs bug
}

bool
parseargs::getline (vec<str> *args, int *linep)
{
  args->setsize (0);
  skipblanks ();
  if (linep)
    *linep = lineno;

  while (str s = getarg ())
    args->push_back (s);
  return args->size ();
}

void
parseargs::error (str msg)
{
  strbuf pref;
  if (filename)
    pref << filename << ":";
  if (lineno)
    pref << lineno << ": ";
  else
    pref << " ";

  fatal << pref << msg << "\n";
}

parseargs::parseargs (str file, int fd)
  : buf (errorbuf), lim (buf), p (buf), filename (file), lineno (0)
{
  if (fd == -1 && (fd = open (file, O_RDONLY, 0)) < 0)
    error (strbuf ("%m"));

  // XXX - should fstat fd for initial size, to optimize for common case
  // when fd is for a regular file.
  size_t pos = 0;
  size_t size = 128;
  buf = static_cast<char *> (xmalloc (size));

  for (;;) {
    ssize_t n = read (fd, buf + pos, size - pos);
    if (n < 0) {
      error (strbuf ("%m"));
      close (fd);
      return;
    }
    if (!n) {
      p = buf;
      lim = buf + pos;
      lineno = 1;
      close (fd);
      return;
    }
    pos += n;
    if (pos == size)
      size <<= 1;
    buf = static_cast<char *> (xrealloc (buf, size));
  }
}

parseargs::~parseargs ()
{
  if (buf != errorbuf)
    xfree (buf);
}
