/* -*-c-*-
 *
 * $Id: mdwopt.c,v 1.1.1.1 1998/06/17 23:44:42 mdw Exp $
 *
 * Options parsing, similar to GNU getopt_long
 *
 * (c) 1996 Mark Wooding
 * Module code (c) 1999 Darren Salt
 */

/*----- Licensing notice --------------------------------------------------*
 * See mdwopt.c.
 */

/*----- Revision history --------------------------------------------------*
 * See mdwopt.c.
 * This version modified to:
 *  - provide a SWI interface;
 *  - use no static data;
 *  - not use the SharedCLibrary;
 *  - not use any stubs.
 */

#pragma no_check_stack

/*----- External dependencies ---------------------------------------------*/

typedef struct {
  int errnum;
  char errmess[252];
} _kernel_oserror;

typedef struct {
  int r[10];
} _kernel_swi_regs;

#define isspace(c) ((c)=='\t' || (c)==' ')
#define isdigit(c) ((unsigned)((c)-'0')<10)

#define EOF (-1)

extern int strcmp(const char *, const char *);
extern int strncmp(const char *, const char *, int);
extern int strlen(const char *);
extern void strncpy(char *, const char *, int);
extern void strcat(char *, const char *);
extern long strtol(const char *, char **, int);
extern int memset(void *, int, int);
extern char *getenv(const char *);

extern char *stpcpy(char *, const char*);
extern void mini_sprintf(char *, const char*, ...);
extern _kernel_oserror *returnerr(int, const char *);
extern char *errmess(void);
extern int checkvar(const char *);

#include "mdwopt.h"

/*----- Configuration things ----------------------------------------------*/

#define PATHSEP '.'

/*----- Global variables --------------------------------------------------*/

extern _kernel_oserror err;

enum {
  ord__permute = 0,			/* Permute the options (default) */
  ord__return = 1,			/* Return non-option things */
  ord__posix = 2,			/* Do POSIX-type hacking */
  ord__negate = 4			/* Magic negate-next-thing flag */
};

/*----- Main code ---------------------------------------------------------*/

/* --- mo__nextWord --- *
 *
 * Arguments:	int argc = number of command line options
 *		char *argv[] = pointer to command line options
 *		mdwopt_data *data = pointer to persistent state
 *
 * Returns:	Pointer to the next word to handle, or 0
 *
 * Use:		Extracts the next word from the command line or environment
 *		variable.
 */

static char *mo__nextWord(int argc, char *const *argv, mdwopt_data *data)
{
  if (data->ind == -1) {
    char *p = data->env;
    char *q;
    while (isspace((unsigned char)*p))
      p++;
    q = p;
    while (*p && !isspace((unsigned char)*p))
      p++;
    data->env = p;
    if (*p)
      *p++ = 0;
    if (p != q)
      return (q);
    data->env = 0;
    data->ind = 1;
  }

  if (data->next == argc)
    return (0);
  return (argv[data->next++]);
}

/* --- mo__permute --- *
 *
 * Arguments:	char *argv[] = pointer to command line arguments
 *		mdwopt_data *data = pointer to persistent data
 *
 * Returns:	--
 *
 * Use:		Moves a command line option into the right place.
 */

static void mo__permute(char *const *argv, mdwopt_data *data)
{
  char **v = (char **)argv;
  if (data->ind != -1) {
    int i = data->next - 1;
    char *p = (v+=i)[0];
    while (i > data->ind) {
      v[0] = v[-1];
      v--;
      i--;
    }
    v[0] = p;
    data->ind++;
  }
}

/* --- mo__findOpt --- *
 *
 * Arguments:	int o = which option to search for
 *		const char *shortopt = short options string to search
 *		mdwopt_data *data = pointer to persistant state
 *
 * Returns:	Pointer to rest of short options string (including magic
 *		characters)
 *
 * Use:		Looks up a short option in the given string.
 */

static const char *mo__findOpt(int o, const char *shortopt,
			       mdwopt_data *data)
{
  const char *p = shortopt;		/* Point to short opts table */
  for (;;) {
    if (!*p)				/* No more options left */
      return (0);

    if (o != *p || (p[1] != '+' && data->order & ord__negate)) {
      while (*++p == '+');		/* Jump any `+' signs */
      p--;
      while (*++p == ':');		/* and any `:' characters */
    }
    else
      return (p + 1);
  }
}

/* --- mdwopt --- *
 *
 * Arguments:	int argc = number of command line arguments
 *		char * const *argv = pointer to command line arguments
 *		const char *shortopt = pointer to short options information
 *		const struct option *longopts = pointer to long opts info
 *		int *longind = where to store matched longopt
 *		mdwopt_data *data = persistent state for the parser
 *		int flags = various useful flags
 *
 * Returns:	Value of option found next, or an error character, or EOF
 *		for the last thing.
 *
 * Use:		Reads options.  The routine should be more-or-less compatible
 *		with standard getopts, although it provides many more
 *		features even than the standard GNU implementation.
 *
 *		The precise manner of options parsing is determined by
 *		various flag settings, which are described below.  By setting
 *		flag values appropriately, you can achieve behaviour very
 *		similar to most other getopt routines.
 *
 *
 *	    How options parsing appears to users
 *
 *		A command line consists of a number of `words' (which may
 *		contain spaces, according to various shell quoting
 *		conventions).  A word may be an option, an argument to an
 *		option, or a non-option.  An option begins with a special
 *		character, usually `-', although `+' is also used sometimes.
 *		As special exceptions, the word containing only a `-' is
 *		considered to be a non-option, since it usually represents
 *		standard input or output as a filename, and the word
 *		containing a double-dash `--' is used to mark all following
 *		words as being non-options regardless of their initial
 *		character.
 *
 *		Traditionally, all words after the first non-option have been
 *		considered to be non-options automatically, so that options
 *		must be specified before filenames.  However, this
 *		implementation can extract all the options from the command
 *		line regardless of their position.  This can usually be
 *		disabled by setting one of the environment variables
 *		`POSIXLY_CORRECT' or `_POSIX_OPTION_ORDER'.
 *
 *		There are two different styles of options: `short' and
 *		`long'.
 *
 *		Short options are the sort which Unix has known for ages: an
 *		option is a single letter, preceded by a `-'.  Short options
 *		can be joined together to save space (and possibly to make
 *		silly words): e.g., instead of giving options `-x -y', a user
 *		could write `-xy'.  Some short options can have arguments,
 *		which appear after the option letter, either immediately
 *		following, or in the next `word' (so an option with an
 *		argument could be written as `-o foo' or as `-ofoo').  Note
 *		that options with optional arguments must be written in the
 *		second style.
 *
 *		When a short option controls a flag setting, it is sometimes
 *		possible to explicitly turn the flag off, as well as turning
 *		it on, (usually to override default options).  This is
 *		usually done by using a `+' instead of a `-' to introduce the
 *		option.
 *
 *		Long options, as popularised by the GNU utilities, are given
 *		long-ish memorable names, preceded by a double-dash `--'.
 *		Since their names are more than a single character, long
 *		options can't be combined in the same way as short options.
 *		Arguments to long options may be given either in the same
 *		`word', separated from the option name by an equals sign, or
 *		in the following `word'.
 *
 *		Long option names can be abbreviated if necessary, as long as
 *		the abbreviation is unique.  This means that options can have
 *		sensible and memorable names but still not require much
 *		typing from an experienced user.
 *
 *		Like short options, long options can control flag settings.
 *		The options to manipulate these settings come in pairs: an
 *		option of the form `--set-flag' might set the flag, while an
 *		option of the form `--no-set-flag' might clear it.
 *
 *		It is usual for applications to provide both short and long
 *		options with identical behaviour.  Some applications with
 *		lots of options may only provide long options (although they
 *		will often be only two or three characters long).  In this
 *		case, long options can be preceded with a single `-'
 *		character, and negated by a `+' character.
 *
 *		Finally, some (older) programs accept arguments of the form
 *		`{"-"<number>}', to set some numerical parameter, typically a
 *		line count of some kind.
 *
 *
 *	    How programs parse options
 *
 *		An application parses its options by calling mdwopt
 *		repeatedly.  Each time it is called, mdwopt returns a value
 *		describing the option just read, and stores information about
 *		the option in a data block.  The value -1 is returned when
 *		there are no more options to be read.  The `?' character is
 *		returned when an error is encountered.
 *
 *		Before starting to parse options, the value data->ind must be
 *		set to 0 or 1. The value of data->err can also be set, to
 *		choose whether errors are reported by mdwopt.
 *
 *		The program's `argc' and `argv' arguments are passed to the
 *		options parser, so that it can read the command line.  A
 *		flags word is also passed, allowing the program fine control
 *		over parsing.  The flags are described above.
 *
 *		Short options are described by a string, which once upon a
 *		time just contained the permitted option characters.  Now the
 *		options string begins with a collection of flag characters,
 *		and various flag characters can be put after options
 *		characters to change their properties.
 *
 *		If the first character of the short options string is `+',
 *		`-' or `!', the order in which options are read is modified,
 *		as follows:
 *
 *		`+'	forces the POSIX order to be used. As soon as a non-
 *			option is found, mdwopt returns -1.
 *
 *		`-'	makes mdwopt treat non-options as being `special'
 *			sorts of option. When a non-option word is found, the
 *			value 0 is returned, and the actual text of the word
 *			is stored as being the option's argument.
 *
 *		`!'	forces the default order to be used.  The entire
 *			command line is scanned for options, which are
 *			returned in order.  However, during this process, the
 *			options are moved in the argv array, so that they
 *			appear before the non- options.
 *
 *		A `:' character may be placed after the ordering flag (or at
 *		the very beginning if no ordering flag is given) which
 *		indicates that the character `:', rather than `?', should be
 *		returned if a missing argument error is detected.
 *
 *		Each option in the string can be followed by a `+' sign,
 *		indicating that it can be negated, a `:' sign indicating that
 *		it requires an argument, or a `::' string, indicating an
 *		optional argument.  Both `+' and `:' or `::' may be given,
 *		although the `+' must come first.
 *
 *		If an option is found, the option character is returned to
 *		the caller.  A pointer to an argument is stored in data->arg,
 *		or NULL is stored if there was no argument. If a negated
 *		option was found, the option character is returned ORred with
 *		gFlag_negated (bit 8 set).
 *
 *		Long options are described in a table.  Each entry in the
 *		table is of type struct option, and the table is terminated
 *		by an entry whose name field is null.  Each option has a
 *		flags word which, due to historical reasons, is called
 *		has_arg.  This describes various properties of the option,
 *		such as what sort of argument it takes, and whether it can be
 *		negated.
 *
 *		When mdwopt finds a long option, it looks the name up in the
 *		table. The index of the matching entry is stored in the
 *		longind variable, passed to mdwopt (unless longind is 0): a
 *		value of -1 indicates that no long option was found. The
 *		behaviour is then dependent on the values in the table entry.
 *		If flag is nonzero, it points to an integer to be modified by
 *		mdwopt.  Usually the value in the val field is simply stored
 *		in the flag variable. If the flag gFlag_switch is set,
 *		however, the value is combined with the existing value of the
 *		flags using a bitwise OR.  If gFlag_negate is set, then the
 *		flag bit will be cleared if a matching negated long option is
 *		found.  The value 0 is returned.
 *
 *		If flag is zero, the value in val is returned by mdwopt,
 *		possibly with bit 8 set if the option was negated.
 *
 *		Arguments for long options are stored in data->arg, as
 *		before.
 *
 *		Numeric options, if enabled, cause the value `#' to be
 *		returned, and the numeric value to be stored in data->opt.
 *
 *		If the flag gFlag_envVar is set on entry, options will be
 *		extracted from an environment variable whose name is built by
 *		capitalising all the letters of the program's name.  (This
 *		allows a user to have different default settings for a
 *		program, by calling it through different symbolic links.)  */

int mdwopt(int argc, char *const *argv,
	   const char *shortopt,
	   const struct option *longopts, int *longind,
	   mdwopt_data *data, int flags)
{
  /* --- Local variables --- */

  char *p, *q, *r;			/* Some useful things to have */
  char *prefix;				/* Prefix from this option */
  int i;				/* Always useful */
  int noarg = ~'?';			/* Standard missing-arg char */

  if (!data) {
    stpcpy(errmess(), "No options data block!");
    return -2;
  }

  /* --- See if this is the first time --- */

  if (data->ind <= 0 || (data->ind == 1 && ~flags & gFlag_noProgName)) {

    /* --- Sort out default returning order --- */

    if (checkvar("_POSIX_OPTION_ORDER") || /* Examine environment for opts */
	checkvar("POSIXLY_CORRECT"))	/* To see if we disable features */
      data->order = ord__posix;		/* If set, use POSIX ordering */
    else
      data->order = ord__permute;	/* Otherwise mangle the options */

    /* --- Now see what the caller actually wants --- */

    switch (shortopt[0]) {		/* Look at the first character */
      case '-':				/* `-' turns on in-orderness */
	data->order = ord__return;
	break;
      case '+':				/* `+' turns on POSIXness */
	data->order = ord__posix;
	break;
      case '!':				/* `!' ignores POSIXness */
	data->order = ord__permute;
	break;
    }

  }

  /* --- Do some initial bodgery --- *
   *
   * The shortopt string can have some interesting characters at the
   * beginning.  We'll skip past them.
   */

  switch (shortopt[0]) {
    case '+':
    case '-':
    case '!':
      shortopt++;
      break;
  }

  if (shortopt[0] == ':') {
    noarg = ~shortopt[0];
    shortopt++;
  }

  if (longind)				/* Allow longind to be null */
    *longind = -1;			/* Clear this to avoid confusion */
  data->opt = -1;			/* And this too */
  data->arg = 0;			/* No option set up here */

  /* --- Now go off and search for an option --- */

  if (!data->list || !*data->list) {
    data->order &= 3;			/* Clear negation flag */

    /* --- Now we need to find the next option --- *
     *
     * Exactly how we do this depends on the settings of the order variable.
     * We identify options as being things starting with `-', and which
     * aren't equal to `-' or `--'.  We'll look for options until:
     *
     *	 * We find something which isn't an option AND order == ord__posix
     *	 * We find a `--'
     *	 * We reach the end of the list
     *
     * There are some added little wrinkles, which we'll meet as we go.
     */

    for (;;) {				/* Keep looping for a while */
      p = mo__nextWord(argc, argv, data); /* Get the next word out */
      if (!p)				/* If there's no next word */
	return (EOF);			/* There's no more now */

      /* --- See if we've found an option --- */

      if ((p[0] == '-' || (p[0] == '+' && flags & gFlag_negation)) &&
	  p[1] != 0) {
	if (strcmp(p, "--") == 0) {	/* If this is the magic marker */
	  mo__permute(argv, data);	/* Stow the magic marker item */
	  return (EOF);			/* There's nothing else to do */
	}
	break;				/* We've found something! */
      }

      /* --- Figure out how to proceed --- */

      switch (data->order & 3) {
	case ord__return:		/* Return each argument */
	  mo__permute(argv, data);	/* Insert word in same place */
	  data->arg = p;		/* Point to the argument */
	  return (0);			/* Return the value */
	case ord__posix:		/* POSIX option order */
	  return (EOF);			/* This is easy */
	case ord__permute:		/* Permute the option order */
	  break;
      }
    }

    /* --- We found an option --- */

    mo__permute(argv, data);		/* Do any permuting necessary */

    /* --- Check for a numeric option --- *
     *
     * We only check the first character (or the second if the first is a
     * sign).  This ought to be enough.
     */

    if (flags & gFlag_numbers && (p[0] == '-' || flags & gFlag_negNumber)) {
      if (((p[1] == '+' || p[1] == '-') && isdigit((unsigned char)p[2])) ||
	  isdigit((unsigned char)p[1])) {
	data->opt = (int)strtol(p + 1, &data->arg, 10);
	while (isspace((unsigned char)data->arg[0]))
	  data->arg++;
	if (!data->arg[0])
	  data->arg = 0;
	return (p[0] == '-' ? '#' : '#' | gFlag_negated);
      }
    }

    /* --- Check for a long option --- */

    if (p[0] == '+')
      data->order |= ord__negate;

    if (((p[0] == '-' && p[1] == '-') ||
	 (flags & gFlag_noShorts && !mo__findOpt(p[1], shortopt, data))) &&
	(~flags & gFlag_noLongs))	/* Is this a long option? */
    {
      int match = -1;			/* Count matches as we go */

      if (p[0] == '+') {		/* If it's negated */
	data->order |= ord__negate;	/* Set the negate flag */
	p++;				/* Point to the main text */
	prefix = "+";			/* Set the prefix string up */
      } else if (p[1] == '-') {		/* If this is a `--' option */
	if ((flags & gFlag_negation) && strncmp(p + 2, "--no-"+2, 3) == 0) {
	  p += 5;			/* Point to main text */
	  prefix = "--no-";		/* And set the prefix */
	  data->order |= ord__negate;	/* Set the negatedness flag */
	} else {
	  p += 2;			/* Point to the main text */
	  prefix = "--";		/* Remember the prefix string */
	}
      } else {
	if ((flags & gFlag_negation) && strncmp(p + 1, "--no-"+2, 3) == 0) {
	  p += 4;			/* Find the text */
	  prefix = "--no-"+1;		/* Set the prefix */
	  data->order |= ord__negate;	/* Set negatedness flag */
	} else {
	  p++;				/* Otherwise find the text */
	  prefix = "--"+1;		/* And remember the prefix */
	}
      }

      for (i = 0; longopts[i].name; i++) { /* Loop through the options */
	if ((data->order & ord__negate) &&
	    (~longopts[i].has_arg & gFlag_negate))
	  continue;			/* If neg and opt doesn't allow */

	r = (char *) longopts[i].name;	/* Point to the name string */
	q = p;				/* Point to the string start */
	for (;;) {			/* Do a loop here */
	  if (*q == 0 || *q == '=') {	/* End of the option string? */
	    if (*r == 0) {		/* If end of other string */
	      match = i;		/* This is the match */
	      goto botched;		/* And exit the loop now */
	    }
	    if (match == -1) {		/* If no match currently */
	      match = i;		/* Then this is it, here */
	      break;			/* Stop looking now */
	    } else {
	      match = -1;		/* Else it's ambiguous */
	      goto botched;		/* So give up right now */
	    }
	  }
	  else if (*q != *r)		/* Otherwise if mismatch */
	    break;			/* Abort this loop */
	  q++, r++;			/* Increment the counters */
	}
      }

    botched:
      if (match == -1) {		/* If we couldn't find a match */
	if (strlen(prefix)+strlen(p) > 80)
	  stpcpy(errmess(), "Unrecognised option");
	else
	  mini_sprintf(errmess(), "%s '%s%s'", "Unrecognised option",
		  prefix, p);
	return ~'?';
      }

      if (longind)			/* Allow longind to be null */
	*longind = match;		/* Store the match away */

      /* --- Handle argument behaviour --- */

      while (*p != 0 && *p != '=')	/* Find the argument string */
	p++;
      p = (*p ? p + 1 : 0);		/* Sort out argument presence */
      q = (char *) longopts[match].name; /* Remember the name here */

      switch (longopts[match].has_arg & 3) {
	case no_argument:
	  if (p) {
	    if (strlen(prefix)+strlen(q) > 80)
	      mini_sprintf(errmess(), "%s %s",
		      "Option", "does not accept arguments");
	    else
	      mini_sprintf(errmess(), "%s '%s%s' %s",
		      "Option", prefix, q, "does not accept arguments");
	    return ~'?';
	  }
	  break;

	case required_argument:
	  if (!p) {			/* If no argument given */
	    p = mo__nextWord(argc, argv, data);

	    if (!p) {			/* If no more arguments */
	      if (strlen(prefix)+strlen(q) > 80)
		mini_sprintf(errmess(), "%s %s",
			"Option", "requires an argument");
	      else
		mini_sprintf(errmess(), "%s '%s%s' %s",
			"Option", prefix, q, "requires an argument");
	      return (noarg);
	    }

	    mo__permute(argv, data);
	  }
	  break;

	case optional_argument:
	  /* Who cares? */
	  break;
      }
      data->arg = p;

      /* --- Do correct things now we have a match --- */

      if (longopts[match].flag) {	/* If he has a flag argument */
	if (longopts[match].has_arg & gFlag_switch) {
	  if (data->order & ord__negate)
	    *longopts[match].flag &= ~longopts[match].val;
	  else
	    *longopts[match].flag |= longopts[match].val;
	} else {
	  if (data->order & ord__negate)
	    *longopts[match].flag = 0;
	  else
	    *longopts[match].flag = longopts[match].val;
	}
	return (0);			/* And return something */
      } else {
	if (data->order & ord__negate)
	  return (longopts[match].val | gFlag_negated);
	else
	  return (longopts[match].val);
      }
    }

    /* --- Do short options things --- */

    else {
      if (p[0] == '+')			/* If starts with a `+' */
	data->order |= ord__negate;
      data->list = p + 1;		/* Omit leading `-'/`+' */
    }
  }

  /* --- Now process the short options --- */

  i = *data->list++;			/* Get the next option letter */
  data->opt = i;			/* Store this away nicely */

  p = (char *) mo__findOpt(i, shortopt, data);
  if (!p) {				/* No more options left */
    mini_sprintf(errmess(), "Unknown option '%c%c'",
	    data->order & ord__negate ? '+' : '-', i);
    return ~'?';
  }

  data->opt = i;			/* Store this for the caller */

  /* --- Sort out an argument, if we expect one --- */

  if (p[0] == ':') {			/* If we expect an option */
    q = (data->list[0] ? data->list : 0); /* If argument expected, use it */
    data->list = 0;			/* Kill the remaining options */
    if (p[1] != ':' && !q) {		/* If no arg, and not optional */

      /* --- Same code as before --- */

      q = mo__nextWord(argc, argv, data); /* Read the next word */
      if (!q) {				/* If no more arguments */
	mini_sprintf(errmess(), "%s '%c%c' %s",
		"Option", data->order & ord__negate ? '+' : '-', i,
		"requires an argument");
	return (noarg);
      }
      mo__permute(argv, data);
    }

    data->arg = q;
  }
  return ((data->order & ord__negate) ? i | gFlag_negated : i);
}


/* Module stuff (DS) */

typedef struct {
  char *argv[256];
  char args[1024];
} argbuf;


/* --- parse_command_string --- *
 *
 * Arguments:	const char *arg = command string (eg. OS_GetEnv output R0)
 *		argbuf *argv = pointer to buffer for command line arguments
 *
 * Returns:	Number of arguments found.
 *
 * Use:		Parses the command string in a C-style way.
 *		argbuf is a 2K buffer, consisting of:
 *			char *argv [256] = argument pointers array
 *			char args [1024] = argument buffer
 *		argv contains pointers into args, into which the individual
 *		arguments present in the command string are placed. (It's a
 *		2K buffer.)
 */
int parse_command_string(const char *arg, argbuf *argv)
{
  const char *p = arg;
  char **const argp = argv->argv;
  char *const s = argv->args;
  int sl = 0;
  int argc = 0;

  if (!p) return 0;
  do {
    int sl_old = sl;
    while (*p == 32) p++;
    if (*p < 32) break;
    if (sl == 1024)
      return -1; /* too long */
    if (*p == '"') {
      for (;;) {
	p++;
	if (*p == '\\') {
	  switch (p[1]) {
	    case '\0': return -2; /* mismatched quote */
	    case '\\':
	    case '"' : p++; if (s) s[sl] = *p; break;
	    default  : if (s) s[sl] = *p;
	  }
	} else {
	  if (*p == '"') break;
	  if (s) s[sl] = *p;
	}
	if (++sl == 1024) return -1; /* too long */
      }
      p++; /* skip close quote */
    } else {
      while (*p > ' ') {
	if (s) s[sl] = *p++;
	if (++sl == 1024) return -1; /* too long */
      }
    }
    if (s) { /* s==0 => argp==0 */
      s[sl] = 0;
      argp[argc] = s+sl_old;
    }
    sl++;
    argc++;
    if (argc == 256) return -3;
  } while (1);
  /* done */
  if (argp) argp[argc] = 0;
  return argc;
}


/* --- reconstitute_command_string --- *
 *
 * Arguments:	const argbuf *argv = ptr to buffer containing cmd line args
 *		char *buf = buffer for reconstituted command string, or 0
 *		int len = size of the buffer pointed to by |buf|
 *		int os = if 0, use C-style quoting, else RISC OS-style
 *
 * Returns:	if buf!=0, the offset of the terminating NUL
 *		if buf==0, the space required for the buffer (inc. term.)
 *
 * Use:		Rebuilds a command string from an argv built by either
 *		MDWOpt_ParseCommandString or the C runtime. The end of the
 *		argv is implied by a null pointer.
 */
static int reconstitute_command_string(const argbuf *argv, char *buf,
				       const int len, const int os_style)
{
  const argbuf *const a = argv;
  const char quote = os_style ? '"' : '\\';
  char *b = buf;
  int p = 0;
  int l = b ? len : 1;

  while (a->argv[p]) {
    const char *s = a->argv[p] - 1;
    int quoted = 0;
    int len = 0;

    while (*++s) {
      if (*s == ' ') quoted = 1;
      if (*s == '"' || *s == quote) len++;
    }
    quoted |= len;
    len += (len ? 3 : 1) + s - a->argv[p];

    if (!b)
      l += len; /* we're just counting... */
    else {
      l -= len;
      if (l < 1) return -1; /* must allow space for the terminating NUL */
      if (p) *b++ = ' ';
      if (!quoted)
	b = stpcpy(b, a->argv[p]);
      else {
	s = a->argv[p] - 1;
	*b++ = '"';
	while (*++s) {
	  if (*s == '"' || *s == quote) *b++ = quote;
	  *b++ = *s;
	}
	*b++ = '"';
      }
    }
    p++;
  }
  if (b) *b = 0;
  return b ? len - l : l;
}


/* --- add_long_option --- *
 *
 * Arguments:	struct option *opt = the long options block
 *		char **optbuf = (r/w) pointer to the option names buffer
 *		int *optsize = (r/w) remaining space in the names buffer
 *		const char *name = the option name
 *		int has_arg = argument flag
 *		int *flag = pointer to flag variable
 *		int val = value to be stored in flag variable
 *
 * Returns:	0 if successful
 *		-1 if no options block supplied
 *		-2 if option names buffer is full
 *		-3 if no option name
 *		*optbuf, *optsize updated
 *
 * Use:		Builds a long options block, one entry per call. The block is
 *		to be used when parsing the arguments list.
 *
 *		The supplied option is appended to the end of the list, and a
 *		new end-of-list marker is created. If you supply an option
 *		names buffer, then the name is copied into this, else the
 *		pointer to the name is placed directly in the options block.
 *
 *		(The options block must be initialised by your code, by
 *		setting its first word to 0.)
 */
int add_long_option(struct option *opt,
		     char **optbuf, int *optsize,
		     const char *name, int has_arg, int *flag, int val)
{
  if (!opt) return -1; /* we must have an options block */
  if (!name) return -3; /* we must have an option name */
  while (opt->name) opt++;

  if (*optbuf) {
    int len = strlen(name)+1;
    if (len > *optsize) return -2; /* whoops, out of space */
    stpcpy(*optbuf, name);
    name = *optbuf;
    *optbuf += len;
    *optsize -= len;
  }

  opt->name = name;
  opt->has_arg = has_arg;
  opt->flag = flag;
  opt->val = val;
  opt[1].name = 0;

  return (int)(opt+1);
}


/* Set up mdwopt_data block.
 * If data == 0 (no block)
 *   get the length of the options variable (if set),
 *   return this plus the size of the data block,
 *   and expect the caller to allocate that much space.
 * Else
 *   fill in the block, reading the options variable into it (if set).
 *
 * argv0 contains the command name, and flags is as passed to mdwopt().
 */
static int init_args(mdwopt_data *data, char *argv0, int flags)
{
  /* Point at opts var buffer, if necessary */
  if (data && !data->estart)
    data->estart = (char *)(data + 1);
  /* First, decide on the program's name */
  if (~flags & gFlag_noProgName) {
    char *p;
    char *q;
    p = q = argv0;
    while (*p) {
      if (*p++ == PATHSEP) q = p;
    }
    if (data) {
      data->prog = q;
      data->ind = data->next = 1;
      data->list = 0;
    }

    /* --- See about environment variables --- */

    if (flags & gFlag_envVar && strlen(q) < 48) {
      char buf[64];
      strncpy(buf, q, 55);
      strcat(buf, "$Options");
      p = getenv(buf); /* try <program>$Options */
      if (!p)
	p = getenv(q); /* try <program> instead */

      if (!data)
	return sizeof(mdwopt_data) + (p ? (strlen(p) + 1) : 0);

      /* --- Copy the options string into a buffer --- */

      if (p) {				/* If it is defined */
	data->env = data->estart;	/* Store the pointer */
	stpcpy(data->estart, p);	/* Copy the text over */
	data->ind = -1;			/* Mark that we're parsing envvar */
      } else {
	data->estart[0] = 0;
      }
    }
    return data ? 0 : sizeof(mdwopt_data);
  } else {
    if (data) data->ind = data->next = 0;
    return 0;
  }
}


/* The SWI handler. */
_kernel_oserror *swi_handler(int swi, _kernel_swi_regs *r, void *pw)
{
  int ret, arg;
  mdwopt_data *data;

  switch (swi) {
    case 0:
      r->r[0] = init_args((mdwopt_data *)r->r[0], (char *)r->r[1], r->r[2]);
      return 0;
    case 1:
      data = (mdwopt_data *)r->r[4];
      ret = mdwopt(r->r[0], (char *const *)r->r[1], (const char *)r->r[2],
		   (const struct option *)r->r[3], &arg, data, r->r[5]);
      r->r[1] = ret < -1 ? ~ret : ret;
      r->r[2] = arg;
      r->r[3] = data->opt;
      r->r[4] = (int)data->arg;
      r->r[5] = data->ind;
      return (ret < -1) ? returnerr(220, 0) : 0;
    case 2:
      ret = parse_command_string((const char *)r->r[0], (argbuf *)r->r[1]);
      switch (ret) {
	case -1: return returnerr(256, "Command line is too long");
	case -2: return returnerr(256, "Mismatched quotation marks");
	case -3: return returnerr(256, "Too many arguments");
	default: r->r[0] = ret; return 0;
      }
    case 3:
      ret = add_long_option((struct option *)r->r[0],
			    (char **)&r->r[1], &r->r[2],
			    (const char *)r->r[3], r->r[4],
			    (int *)r->r[5], r->r[6]);
      switch (ret) {
	case -1: return returnerr(256, "Missing long options buffer");
	case -2: return returnerr(256, "Buffer overflow");
	case -3: return returnerr(256, "Missing long option name");
	default: r->r[0] = ret; return 0;
      }
    case 4:
      ret = reconstitute_command_string((const argbuf *)r->r[0],
					(char *)r->r[1], r->r[2], r->r[3]);
      switch (ret) {
	case -1: return returnerr(256, "Buffer overflow");
	default: r->r[0] = ret; return 0;
      }
    default: return returnerr(0x1E6, "SWI number out of range");
  }
}


/*----- That's all, folks -------------------------------------------------*/
