/*
 * $Id$
 *
 * $Log$
 * Revision 1.6  1998/12/17 14:20:00  couet
 * - new routine to delete the temporary vectors created
 *  by $SIGMA. On NT a direct call to kuvdel didn't work
 *
 * Revision 1.5  1997/03/24 11:10:18  gunter
 * Mods for WinNT; Apply F77_ENTRY to several Ku... routines; this
 * was missing from Kutime, Kutim0, Kustat, Kugeti, Kugetr and Kunpar.
 *
 * Revision 1.4  1997/03/11 09:43:40  cremel
 * Bug correction in command WAIT (after answering "Q" to a "WAIT" statement,
 * the ability to issue several HISTOGRAM/PLOT statements on a single line,
 * separated by semicolons, disappears).
 * kexec.c : add a test on the variable "kc_flags.in_macro".
 * kuvers.c : increase version number to 2.07/13
 *
 * Revision 1.3  1996/05/01 12:08:36  gunter
 * Replace several strcpy by memmove
 *
 * Revision 1.2  1996/04/11 09:41:52  cernlib
 * Protect for an empty string in fill_arg_list
 *
 * Revision 1.1.1.1  1996/03/08 15:32:56  mclareni
 * Kuip
 *
 */
/*-- Author :    Alfred Nathaniel   14/08/92*/
/* kexec.c: command execution */

#include "kuip/kuip.h"
#include "kuip/kfor.h"
#include "kuip/kcom.h"
#include "kuip/ksig.h"
#include "kuip/kmenu.h"
#include "kuip/kflag.h"
#include "kuip/klink.h"
#include "kuip/khash.h"
#include "kuip/kalias.h"


static KmCommand  *curr_cmd;
static int   next_arg;
static char *list_arg;


static int   call_action( KmCommand* );
static int   exec_multiple_cmds( const char*, int, int(*)() );
static int   exec_single_cmd( const char*, int, int(*)(), int );
static int   exec_template( const char*, const char*, int, int(*)() );
static int   fill_arg_list( KmCommand*, char*, int, int(*)(), int );
static char* find_value( char*, INTEGER*, REAL* );
static int   get_chval( char*, int, char*(*)() );
static char* next_value( const char* );
static const char* next_word( const char* );
static const char* prompt_argument( KmParameter* );
static int   valid_option( KmParameter*, const char*, int );


/*
 * action routine for /MACRO/DEFAULTS
 */
int kxmdefaults()
{
  char *path = ku_gets();
  char *mdef = ku_getc();

  if( strcmp( path, "?" ) == 0 ) {
    ku_alfa();
    if( kc_flags.macro_path == NULL ) {
      printf( " *** No macro search path defined\n" );
    }
    else {
      printf( " Macro search path: %s\n", kc_flags.macro_path );
    }
  }
  else if( strcmp( path, "." ) == 0 ) {
    if( kc_flags.macro_path != NULL ) {
      free( kc_flags.macro_path );
      kc_flags.macro_path = NULL;
    }
    kc_flags.macro_search = KmMACRO_COMMAND;
    kc_value.defaults_path = ".";
  }
  else {
    if( kc_flags.macro_path != NULL )
      free( kc_flags.macro_path );
    kc_flags.macro_path = strdup( path );
    kc_value.defaults_path = kc_flags.macro_path;
    kc_value.defaults_order = "-COMMAND";
  }

  if( strcmp( mdef, "A" ) == 0 || strcmp( mdef, "AUTO" ) == 0 ) {
    kc_flags.macro_search = KmMACRO_AUTO;
    kc_value.defaults_order = "-AUTO";
  }
  else if( strcmp( mdef, "AR" ) == 0 || strcmp( mdef, "AUTOREVERSE" ) == 0 ) {
    kc_flags.macro_search = KmMACRO_AUTOREVERSE;
    kc_value.defaults_order = "-AUTOREVERSE";
  }
  else if( strcmp( mdef, "C" ) == 0 || strcmp( mdef, "COMMAND" ) == 0 ) {
    kc_flags.macro_search = KmMACRO_COMMAND;
    kc_value.defaults_order = "-COMMAND";
  }
  else {
    char *value;
    switch( kc_flags.macro_search ) {
    case KmMACRO_COMMAND:
      value = "Command";
      break;
    case KmMACRO_AUTO:
      value = "Auto";
      break;
    case KmMACRO_AUTOREVERSE:
      value = "AutoReverse";
      break;
    default:
      value = "Undefined";
      break;
    }
    ku_alfa();
    printf( " Macro search order is %s\n", value );
  }

  return 0;
}


/*
 * action routine for /MACRO/TRACE
 */
int kxmtrace()
{
  char *onoff = ku_getc();
  char *level = ku_getc();

  if( strcmp( onoff, "ON" ) == 0 ) {
    kc_flags.trace_on = 1;
  }
  else {
    kc_flags.trace_on = 0;
  }

  if( strcmp( level, "WAIT" ) == 0 ) {
    kc_flags.trace_wait = 1;
    kc_flags.trace_wait_save = 1;
  }
  else if( strcmp( level, "FULL" ) == 0 ) {
    kc_flags.trace_full = 1;
    kc_flags.trace_debug = 0;
  }
  else if( strcmp( level, "DEBUG" ) == 0 ) {
    kc_flags.trace_full = 1;
    kc_flags.trace_debug = 1;
  }
  else if( strcmp( level, "TEST" ) == 0 ) {
    kc_flags.trace_test = 1;
  }
  else {
    kc_flags.trace_wait = 0;
    kc_flags.trace_wait_save = 0;
    kc_flags.trace_full = 0;
    kc_flags.trace_debug = 0;
    kc_flags.trace_test = 0;
  }

  return 0;
}


int exec_cmd_string( const char *cmd_string,
                    int interactive,
                    int (*display_panel)() )
{
  int status = 0;

  check_edit_server();

  if( !kc_flags.in_macro
     && kc_flags.in_application && !kc_flags.appl_called ) {

    if( strcasecmp( cmd_string, kc_flags.appl_exit ) != 0 ) {
      KmCommand *cmd = kc_flags.appl_cmd;

      /* call the application command with argument string */
      reset_arg_list( cmd );
      cmd->argc = 1;
      cmd->argv = (char**)malloc( sizeof(char*) );
      cmd->argoffs = (int*)malloc( sizeof(int) );
      cmd->argv[0] = strdup( cmd_string );
      cmd->argoffs[0] = 0;
      cmd->argline = strdup( cmd_string );
      status = exec_decoded_cmd( kc_flags.appl_cmd );
    }
    else {
      /* leave application */

      /* flag can be reset only here because it is tested in KUAPPL */
      kc_flags.in_application = 0;
      if( kc_flags.curr_prompt != NULL )
        free( kc_flags.curr_prompt );
      kc_flags.curr_prompt = strdup( kc_value.set_prompt );
    }
  }
  else if( cmd_string != NULL && cmd_string[0] != '\0' ) {

    kc_alias.substitutions = 100; /* protect against recursive aliases */

    status = exec_multiple_cmds( cmd_string, interactive, display_panel );

    if( kc_flags.last_cmd != NULL )
      free( kc_flags.last_cmd );
    kc_flags.last_cmd = strdup( cmd_string ); /* for $LAST function */
#ifdef DBMALLOC
    malloc_mark( kc_flags.last_cmd );
#endif
  }

  if( kjmpaddr.user_grfl_F != NULL )
    (*kjmpaddr.user_grfl_F)();  /* flush graphics output */

  return status;
}


/*
 * split the line into semicolon separated commands and execute each of them
 * substitute command aliases
 * if -AutoReverse try auto exec
 * apply COMMAND logic
 * execute command
 */
static
int exec_multiple_cmds( const char *cmd_string,
                       int interactive,
                       int (*display_panel)() )
{
  char *cmd_line = strdup( cmd_string );
  char *line = cmd_line;
  int status = 0;

  while( line[0] != '\0' ) {
    int want_prompt = interactive;
    int block_panel;
    char *tail;

    while( *line == ' ' )       /* kill leading blanks */
      line++;

    if( line[0] == '*' ) {      /* comment line */
      break;
    }
    else if( line[0] == '+' ) { /* suppress panel display */
      line++;
      want_prompt = 0;
    }
    else if( line[0] == '-' ) { /* force panel display */
      line++;
      want_prompt = -1;
    }
    while( *line == ' ' )
      line++;

    /* extract single command */
    tail = split_at_semicolon( line );
    if( line[0] == '\0' ) {
      line = tail;
      /* ignore empty lines */
      continue;
    }
    /* for multiple commands block the Motif panel until OK or Cancel */
    block_panel = (tail[0] != '\0');

    status = exec_single_cmd( line, want_prompt, display_panel, block_panel );

    if( status == KUMAC_UNWIND )
      break;

    if( tail[0] == '&' ) {
      /* cmd1 ;& cmd2 : execute second command only if first one succeeded */
      if( status != 0 )
        break;
      line = tail + 1;
    }
    else if( tail[0] == '!' ) {
      /* cmd1 ;! cmd2 : execute second command only if first one failed */
      if( status == 0 )
        break;
      line = tail + 1;
    }
    else {
      /* cmd1 ; cmd2 : execute second command anyway */
      line = tail;
    }
  }
  free( cmd_line );
  return status;
}


static
int exec_single_cmd( const char *cmd_line,
                    int interactive,
                    int (*display_panel)(),
                    int block_panel )
{
  KmCommand **cmd_list;
  KmCommand *cmd;
  char *line;
  char *cmd_path;
  char *cmd_args;
  char *p;
  int status;

  line = strdup( cmd_line );

  if( kc_flags.in_macro ) {

    /* substitute system functions before variable arguments are replaced */
    if( (p = subst_sysfun( line, 1 )) != NULL ) {
      free( line );
      line = p;
    }
  }

  if( (p = subst_var_alias( line )) != NULL ) {
    free( line );
    line = p;
  }

  if( (p = subst_cmd_alias( line )) != NULL ) {
    free( line );
    line = p;

    /* start again because alias value can contain several commands */
    status = exec_multiple_cmds( line, interactive, display_panel );
    free( line );
    return status;
  }

  if( (p = strchr( line, '=' )) != NULL ) {
    /* variable assignment */
    char *name = strtrim( strndup( line, p - line ) );

    if( hash_lookup( kc_alias.global_value, name, NULL ) != NULL ) {
      char *value = ku_expr( p + 1 );

      hash_insert( kc_alias.global_value, name, value, 0 );
      free( value );
      free( name );
      return 0;
    }
    free( name );
  }

  if( kc_alias.translate ) {
    static char _KUIP_ALIAS_[] = "/KUIP/ALIAS/";

    /* separate command path */
    cmd_path = strdup( line );
    if( (p = strchr( cmd_path, ' ' )) != NULL )
      *p = '\0';

    /* substitute argument aliases except for ALIAS commands */
    cmd = search_command( cmd_path, &cmd_list );
    if( (cmd == NULL || strncmp( cmd->path, _KUIP_ALIAS_,
                                (sizeof _KUIP_ALIAS_)-1 ) != 0)
       && (p = subst_arg_alias( line )) != NULL ) {
      free( line );
      line = p;
    }
    free( cmd_path );
  }

  /* substitute system functions */
  if( (p = subst_sysfun( line, 1 )) != NULL ) {
    free( line );
    line = p;
  }

  if( kc_flags.in_macro && kc_flags.trace_on ) {

    if( kc_flags.trace_full ) {
      ku_alfa();
      printf( " >>>>>> %s\n", line );
    }

    if( kc_flags.trace_wait ) {
      switch( ku_more( "Execute", line ) ) {
      case 1:                   /* Yes */
        break;
      case 2:                   /* No */
        free( line );
        return -1;
        /*break;*/
      case 3:                   /* Quit */
        free( line );
        kc_flags.quit_macro = 1;
        return KUMAC_UNWIND;
        /*break;*/
      case 4:                   /* Go */
        kc_flags.trace_wait = 0; /* no more questions */
        break;
      }
    }
  }

  cmd_path = strdup( line );
  if( (p = strchr( cmd_path, ' ' )) != NULL ) {
    *p = '\0';
    cmd_args = strdup( p + 1 );
  }
  else {
    cmd_args = strdup( "" );
  }

  if( !kc_flags.in_macro && kc_flags.macro_search == KmMACRO_AUTOREVERSE ) {
    /* look for macro before commands */
    char *fname = strdup( cmd_path );
    char *hash = strchr( fname, '#' );
    if( hash != NULL )          /* in case "mfile#mname" chop off "#mname" */
      *hash = '\0';

    if( fsearch( fname, "kumac", kc_flags.macro_path ) != NULL ) {
      free( cmd_path );
      free( cmd_args );
      cmd_path = strdup( "/MACRO/EXEC" );
      cmd_args = strdup( line );
    }
    free( fname );
  }

  if( (cmd = search_command( cmd_path, &cmd_list )) == NULL &&
     !kc_flags.in_macro && kc_flags.macro_search == KmMACRO_AUTO ) {
    /* look for macro if command not found */
    char *fname = strdup( cmd_path );
    char *hash = strchr( fname, '#' );
    if( hash != NULL )  /* in case "mfile#mname" chop off "#mname" */
      *hash = '\0';

    if( fsearch( fname, "kumac", kc_flags.macro_path ) != NULL ) {
      cmd = search_command( "/MACRO/EXEC", NULL );
      free( cmd_args );
      cmd_args = strdup( line );
    }
    free( fname );
  }

  if( cmd == NULL ) {
    if( cmd_list == NULL && kc_value.set_command != NULL ) {
      status = exec_template( line, kc_value.set_command,
                             interactive, display_panel );
    }
    else {
      if( kjmpaddr.disp_cmd_list_C != NULL ) {
        (*kjmpaddr.disp_cmd_list_C)(cmd_path, cmd_list, 0);
      } else {
        print_cmd_list( cmd_list, cmd_path );
      }
      status = -1;
    }
  }
  else {
    status = fill_arg_list( cmd, cmd_args,
                           interactive, display_panel, block_panel );
  }
  free( cmd_path );
  free( cmd_args );
  free( line );

  /* delete the temp vectors created by $SIGMA system function */
  if ( kc_flags.temp_vectors > 0 ) {
    INTEGER NB = kc_flags.temp_vectors;
    Kxdsig ( &NB );
    kc_flags.temp_vectors = 0;
  }

  /* delete the temp vectors created for non-contiguous subvector */
  if( Kcutil.NVADD > 0 ) {
    INTEGER IX = Pawc->IXPAWC + 1;
    Mzwipe( &IX );
    Kcutil.NVADD = 0;
  }

  return status;
}


/*
 * action routine for /KUIP/SET_SHOW/APPLICATION
 */
int kxapplication()
{
  char *appl_path = ku_getc();
  char *appl_exit = ku_gets();

  return ku_sapp( appl_path, appl_exit );
}


char *ku_appl( int *appl_luno,
              int *in_macro )
{
  if( kc_flags.in_macro && kc_flags.in_application )
    *appl_luno = kc_flags.appl_luno;
  else
    *appl_luno = 5;
  *in_macro = kc_flags.in_macro;
  return kc_flags.appl_exit;
}


void F77_ENTRY_IIC(Kuappl,lun,macflg,chexit)
/* { */
  int appl_luno;
  int in_macro;
  char *appl_exit = ku_appl( &appl_luno, &in_macro );
  *lun = appl_luno;
  *macflg = in_macro;
  fstrset( chexit, len_chexit, appl_exit );
}


int ku_sapp( const char *appl_path,
            const char *appl_exit )
{
  if( appl_path[0] == '\0' ) {
    kc_flags.in_application = 0;
    if( kc_flags.curr_prompt != NULL )
      free( kc_flags.curr_prompt );
    kc_flags.curr_prompt = strdup( kc_value.set_prompt );
    return 0;
  }
  if( (kc_flags.appl_cmd = search_command( appl_path, NULL )) == NULL ) {
    printf( " *** Unknown application %s\n", appl_path );
    return -1;
  }
  kc_flags.in_application = 1;
  if( kc_flags.curr_prompt != NULL )
    free( kc_flags.curr_prompt );
  if( strstr( kc_value.set_prompt, "[]" ) != NULL )
    kc_flags.curr_prompt = str2dup( kc_flags.appl_cmd->name, " []" );
  else
    kc_flags.curr_prompt = str2dup( kc_flags.appl_cmd->name, " >" );

  if( appl_exit[0] == '\0' )
    appl_exit = "EXIT";

  if( kc_flags.appl_exit != NULL )
    free( kc_flags.appl_exit );
  kc_flags.appl_exit = strdup( appl_exit );

  if( kc_flags.in_macro ) {
    kc_flags.appl_stream = fopen( kc_flags.appl_file, "w" );
  }
  return 0;
}


void F77_ENTRY_CC(Kusapp,chpath,chexit)
/* { */
  char *appl_path = fstrdup( chpath, len_chpath );
  char *appl_exit = fstrdup( chexit, len_chexit );
  ku_sapp( appl_path, appl_exit );
  free( appl_path );
  free( appl_exit );
}


/*
 * action routine for /MACRO/DATA
 */
int kxmdata()
{
  int appl_luno;
  int in_macro;
  char *appl_exit = ku_appl( &appl_luno, &in_macro );
  FILE *stream = fopen( ku_fcase( appl_exit ), "w" );
  char line[256];

  if( stream == NULL ) {
    printf( " *** MACRO/DATA: cannot open file %s\n", appl_exit );
    return 1;
  }
  while( ku_read( appl_luno, line, 255 ) >= 0
        && strcasecmp( appl_exit, line ) != 0 ) {
    fprintf( stream, "%s\n", line );
  }
  fclose( stream );

  return 0;
}


/*
 * action routine for /KUIP/SET_SHOW/TIMING
 */
int kxtiming()
{
  char *set_ON  = "ON";
  char *set_OFF = "OFF";
  char *set_ALL = "ALL";
  char *value = ku_getc();
  KmTiming timing;

  if( strcmp( value, set_OFF ) == 0 ) {
    timing = KmTIMING_OFF;
    kc_value.set_timing = set_OFF;
  }
  else if( strcmp( value, set_ALL ) == 0 ) {
    timing = KmTIMING_ALL;
    kc_value.set_timing = set_ALL;
  }
  else {
    timing = KmTIMING_ON;
    kc_value.set_timing = set_ON;
  }

  if( kc_flags.timing != timing ) {
    if( timing == KmTIMING_OFF ) {
      kc_flags.real_time = time( NULL );
      kc_flags.user_time = clock();
    }
    else
      ku_time( kc_flags.real_time, kc_flags.user_time );
    kc_flags.timing = timing;
  }

  return 0;
}


/*
 * action routine for /KUIP/SET_SHOW/COMMAND
 */
int kxcommand()
{
  char *value = ku_gets();

  ku_cmdl( value );

  return 0;
}


void F77_ENTRY_C(Kucmdl,chpath)
/* { */
  char *path = fstrdup( chpath, len_chpath );

  ku_cmdl( path );
  free( path );
}


void ku_cmdl( const char *template )
{
  if( template[0] == '\0' ) {
    ku_alfa();
    if( kc_value.set_command == NULL )
      printf( " COMMAND line not defined\n" );
    else
      printf( " COMMAND line is : %s\n", kc_value.set_command );
  }
  else if( strcmp( template, "*" ) == 0 ) {
    if( kc_value.set_command != NULL ) {
      free( kc_value.set_command );
      kc_value.set_command = NULL;
    }
  }
  else {
    if( kc_value.set_command != NULL )
      free( kc_value.set_command );
    kc_value.set_command = strdup( template );
  }
}


/*
 * substitute template and execute
 */
static
int exec_template( const char *line,
                  const char *template,
                  int interactive,
                  int (*display_panel)() )
{
  char *old_template = kc_value.set_command;
  char *new_line = strdup( "" );
  int status;
  int i;

  for( i = 0; template[i] != '\0'; i++ ) {

    if( template[i] == '$' ) {

      if( template[i+1] == '*' ) {
        new_line = mstrcat( new_line, line );
        i++;
        continue;
      }

      else if( template[i+1] >= '1' && template[i+1] <= '9' ) {
        int n = template[i+1] - '1';
        char *args = strdup( line );
        char *word = strqtok( args );
        while( word != NULL && n-- > 0 )
          word = strqtok( NULL );
        if( word != NULL )
          new_line = mstrcat( new_line, word );
        free( args );
        i++;
        continue;
      }
    }

    new_line = mstrccat( new_line, template[i], 1 );
  }

  /* avoid infinite recursion of template is an invalid command */
  kc_value.set_command = NULL;
  status = exec_multiple_cmds( new_line, interactive, display_panel );
  free( new_line );
  kc_value.set_command = old_template;
  return status;
}


/*
 * check if string contains only valid options
 */
static
int valid_option( KmParameter *par,
                 const char *opt_string,
                 int len_opt_string )
{
  int valid = 1;

  if( par->range_count > 0 ) {  /* accept everything if no range defined */
    char *options = strupper( strndup( opt_string, len_opt_string ) );
    int maxlen = mstrlen( par->range_value, par->range_count );
    int n;

    /*
     * start with longest range values first because short ones
     * could be substrings of longer ones
     */
    for( n = maxlen; n > 0 && *options != '\0'; n-- ) {
      int j;

      for( j = 0; j < par->range_count; j++ ) {
        int len = strlen( par->range_value[j] );
        if( len == n ) {
          char *val = strupper( strdup( par->range_value[j] ) );
          char *p = strstr( options, val );
          free( val );
          if( p != NULL ) {
            /* remove this option */
	    memmove( p, p + len , strlen(p+len) +1);
/* overlapping            strcpy( p, p + len );*/
          }
        }
      }
    }

    for( n = strlen( options ); n > 0 && options[n-1] == ' '; n-- )
      options[n-1] = '\0';      /* strip trailing blanks */

    if( *options != '\0' )
      valid = 0;                /* option not in range */

    free( options );
  }

  return valid;
}


void reset_arg_list( KmCommand *cmd )
{
  /* clear current values */
  if( cmd->argc > 0 ) {
    int i;

    for( i = 0; i < cmd->argc; i++ )
      free( cmd->argv[i] );
    cmd->argc = 0;
    free( (char*)cmd->argv );
    cmd->argv = NULL;
    free( cmd->argline );
    cmd->argline = NULL;
    free( (char*)cmd->argoffs );
    cmd->argoffs = NULL;
  }
}


/*
 * locate the next blank outside quotes and return pointer to it
 */
static
const char *next_word( const char *line )
{
  int quoted = 0;
  while( *line != '\0' ) {
    if( *line == '\'' )
      quoted = !quoted;
    else if( *line == ' ' && !quoted )
      return line;
    line++;
  }
  return line;
}


/*
 * remove quotes and string concatenations
 */
char *clean_word( char *word )
{
  char *p;
  int quoted = 0;
  int strip_quotes;

  for( p = word; p[0] != '\0'; p++ ) {

    if( p[0] == '\'' ) {

      if( !quoted ) {
        quoted = 1;
      }

      else if( p[1] == '/' && p[2] == '/' && p[3] == '\'' ) {
        /* contract "'abc'//'def'" to "'abcdef'" */
	  memmove(p, p + 4 , strlen(p+4) + 1);
/*        strcpy( p, p + 4 );*/
        p--;
      }

      else if( p[1] == '/' && p[2] == '/' && p[3] != '\0' ) {
        char *conc = strstr( p + 3, "//" );
        if( conc != NULL ) {
          *conc = '\0';
          if( strchr( p + 3, '\'' ) != NULL ) {
            *conc = '/';
            conc = NULL;
          }
          else {
            *conc = '/';
          }
        }
        if( conc == NULL ) {
          /* contract "'abc'//def" to "'abcdef'" */
	    memmove( p, p + 3, strlen(p+3) + 1 );
/*          strcpy( p, p + 3 );*/
          strcat( p, "'" );
        }
        else {
          /* contract "'abc'//def//ghi" to "'abcdef'//ghi" */
          /*               ^p    ^conc */
          /*strncpy( p, p + 3, conc - p - 3 );*/
          memmove( p, p + 3, conc - p - 3 ); /* "'abcdefdef//ghi" */ 
          conc[-3] = '\'';                   /* "'abcdef'ef//ghi" */
	  memmove( conc - 2, conc, strlen(conc) + 1);
	  /*          strcpy( conc - 2, conc );*/
        }
      }

      else {
        quoted = 0;
      }
    }

    else if( !quoted && p[1] == '/' && p[2] == '/' ) {

      if( p[3] == '\'' ) {
        /* contract "abc//'def'" to "'abcdef'" */
        char *s = str2dup( "'", word );
        int n = p - word;
        strcpy( s + n + 2, p + 4 );
        strcpy( word, s );
        free( s );
        p++;
        quoted = 1;
      }
      else if( p[3] != '\0' ) {
        /* contract "abc//def" to "abcdef"; leave "abc//" unchanged */
	memmove( p + 1, p + 3, strlen(p + 3) + 1 );
/*        strcpy( p + 1, p + 3 );*/
      }
    }

  }

  strip_quotes = ( word[0] == '\'' && word[strlen(word)-1] == '\'' );

  if( strip_quotes ) {
    /* test if there are transitions between quoted and not quoted */
    for( p = word + 1; p[0] != '\0'; p++ ) {
      if( p[0] == '\'' ) {
        if( p[1] == '\'' ) {
          /* "'abc''def'" is okay */
          p++;
        }
        else if( p[1] != '\0' ) {
          /* in "'abc'def'ghi'" we want to keep the quotes */
          strip_quotes = 0;
          break;
        }
      }
    }
  }

  for( quoted = 0, p = word; p[0] != '\0'; p++ ) {
    if( p[0] == '\'' ) {
      if( !quoted ) {
        quoted = 1;
      }
      else if( p[1] == '\'' ) {
        /* contract "'abc''def'" to "'abc'def'" */
	memmove( p, p + 1, strlen(p+1) + 1 );
/*        strcpy( p, p + 1 );*/
      }
      else {
        quoted = 0;
      }
    }
  }

  if( strip_quotes ) {
    /* contract "'abcdef'" to "abcdef" */
    for( p = word; p[2] != '\0'; p++ )
      p[0] = p[1];
    p[0] = '\0';
  }

  return word;
}


/*
 * parse the command string
 * find the corresponding command structure
 * fill the argument list
 * check the argument list for completeness and validity
 * return the pointer to the command structure or NULL if something is wrong
 *
 * interactive ==  1 : prompt for missing mandatory parameters or wrong values
 * interactive ==  0 : bail out if something is wrong
 * interactive == -1 : display panel in any case
 */
static
int fill_arg_list( KmCommand *cmd,
                  char *arg_line,
                  int want_prompt,
                  int (*display_panel)(), /* routine to display Motif panel */
                  int block_panel )
{
  char *macro_args = NULL;
  int i;

  reset_arg_list( cmd );
  while( *arg_line == ' ' )
    arg_line++;
  if ( strlen(arg_line) > 0 ) {
    char *p=arg_line + strlen(arg_line) -1;
    while (*p == ' ' )
      *p--='\0';
  }

  if( strcasecmp( cmd->name, "EXEC" ) == 0 ) {
    /*
     * special treatment for "EXEC mname args":
     * chop off arg_line after mname and keep quoting in macro arguments
     */
    macro_args = arg_line;
    while( *macro_args != ' ' && *macro_args != '\0' )
      macro_args++;
    while( *macro_args == ' ' )
      macro_args++;
    if( *macro_args != '\0' )
      macro_args[-1] = '\0';
    else
      macro_args = NULL;
  }

  if( *arg_line != '\0' ) {
    const char *argv = arg_line;
    int argc = 0;
    int blanks = 0;

    while( 1 ) {
      const char *next_argv = next_word( argv );
      int len_argv = next_argv - argv;
      int next_argc = argc + 1;
      char *alloced_argv;
      const char *value;

      if( len_argv == 1 && argv[0] == '!' && argc < cmd->total ) {
        /* "!" == use default value */
        if( (argv = cmd->par[argc]->dfault) != NULL )
          len_argv = strlen( argv );
      }

      else if( len_argv == 2 && argv[0] == '!' && argv[1] == '!'
              && argc < cmd->total ) {
        /* "!?" == use last value or default */
        if( (argv = cmd->par[argc]->last) != NULL
           || (argv = cmd->par[argc]->dfault) != NULL )
          len_argv = strlen( argv );
      }

      else if( len_argv > 1 && argv[0] == '-' && argc < cmd->total
              && (cmd->par[argc]->flags & KmFLAG_MINUS) == 0 ) {
        /* "-value" for a parameter without explicit MINUS flag */
        int try_chopt = 0;

        if( cmd->par[argc]->type == KmTYPE_INT ) {
          /* make sure that it is not an integer number */
          for( i = 1; i < len_argv; i++ ) {
            if( !isdigit( argv[i] ) ) {
              try_chopt = 1;
              break;
            }
          }
        }
        else if( cmd->par[argc]->type == KmTYPE_REAL ) {
          /* make sure that it is not a real number */
          if( isdigit( argv[1] ) || argv[1] == '.' ) {
            for( i = 2; i < len_argv; i++ ) {
              if( strchr( "-12345.678eE+90", argv[i] ) == NULL ) {
                try_chopt = 1;
                break;
              }
            }
          }
          else {
            try_chopt = 1;
          }
        }
        else {
          try_chopt = 1;
        }

        if( try_chopt ) {
          /* try if there is a option parameter for which value is valid */
          for( i = 0; i < cmd->total; i++ ) {
            KmParameter *par = cmd->par[i];
            if( par->type == KmTYPE_OPTION ) {
              if( valid_option( par, argv + 1, len_argv - 1 ) ) {
                next_argc = argc; /* do not increment argument count */
                argc = i;
                argv++;
                len_argv--;
                break;
              }
            }
          }
        }
      }

      else if( (value = strchr( argv, '=' )) != NULL && value < next_argv ) {
        int len = value - argv;
        /* argument is NAME=VALUE */
        for( i = 0; i < cmd->total; i++ ) {
          KmParameter *par = cmd->par[i];
          if( len >= par->abbrev && len <= strlen( par->name )
             && strncasecmp( argv, par->name, len ) == 0 ) {
            next_argc = i + 1; /* move argument count */
            argc = i;
            argv += len + 1;
            len_argv -= len + 1;
            break;
          }
        }
      }

      if( argv != NULL )
        alloced_argv = clean_word( strndup( argv, len_argv ) );
      else
        alloced_argv = NULL;

      if( cmd->argc < argc + 1 ) {
        int k;
        /* adjust argument arrays */
        if( cmd->argc == 0 ) {
          cmd->argv = (char**)malloc( (argc+1) * sizeof(char*) );
          cmd->argoffs = (int*)malloc( (argc+1) * sizeof(int) );
        }
        else {
          cmd->argv = (char**)realloc( (char*)cmd->argv,
                                      (argc+1) * sizeof(char*) );
          cmd->argoffs = (int*)realloc( (char*)cmd->argoffs,
                                       (argc+1) * sizeof(int) );
        }
        for( k = cmd->argc; k < argc + 1; k++ ) {
          cmd->argv[k] = NULL;
          cmd->argoffs[k] = 0;
        }
        cmd->argc = argc + 1;
      }

      /* assign argument value */
      if( cmd->argv[argc] == NULL ) {
        if( argc < cmd->total
           && (cmd->par[argc]->flags & KmFLAG_VARARG) != 0 ) {
          /* protect commas inside the value */
          char *p;
          while( (p = strchr( alloced_argv, ',' )) != NULL )
            *p = '\n';
        }
        cmd->argv[argc] = alloced_argv;
        cmd->argoffs[argc] = blanks;
        blanks = 0;
      }
      else if( alloced_argv != NULL ) {
        /* append to existing value */
        if( argc < cmd->total && cmd->par[argc]->type == KmTYPE_OPTION ) {
          /* options are simply concatenated */
          cmd->argv[argc] = mstrcat( cmd->argv[argc], alloced_argv );
        }
        else if( argc < cmd->total
                && (cmd->par[argc]->flags & KmFLAG_VARARG) != 0 ) {
          /* separate by comma and protect commas inside the value */
          char *p;
          while( (p = strchr( alloced_argv, ',' )) != NULL )
            *p = '\n';
          cmd->argv[argc] = mstr2cat( cmd->argv[argc], ",", alloced_argv );
        }
        else {
          /* other argument are separate by blank */
          cmd->argv[argc] = mstr2cat( cmd->argv[argc], " ", alloced_argv );
        }
        free( alloced_argv );   /* malloc'ed in clean_word() */
      }

      if( argc < cmd->total ) {
        /* check that a constant has not been assigned a different value */
        KmParameter *par = cmd->par[argc];

        if( (par->flags & KmFLAG_CONST) != 0
           && strcmp( cmd->argv[argc], par->dfault ) != 0 ) {
          printf( " *** Parameter %s of %s is defined as a constant!\n",
                 par->name, cmd->name );
          free( cmd->argv[argc] );
          cmd->argv[argc] = strdup( par->dfault );
        }
      }

      if( *next_argv != '\0' ) {
        argv = next_argv + 1;
        while( *argv == ' ' ) {
          argv++;
          blanks++;
        }
        if( argc >= cmd->total
           || (cmd->par[argc]->flags & KmFLAG_VARARG) == 0 )
          argc = next_argc;
      }
      else {
        for( i = 0; i < cmd->argc; i++ ) {
          if( i == 0 )
            cmd->argline = strdup( "" );
          else
            cmd->argline = mstrccat( cmd->argline, ' ', cmd->argoffs[i] + 1 );

          cmd->argoffs[i] = strlen( cmd->argline );
          if( cmd->argv[i] == NULL ) {
            char *dfault = NULL;
            if( i < cmd->total ) {
              KmParameter *par = cmd->par[i];
              if( par->dfault != NULL )
                dfault = par->dfault;
              else if( i >= cmd->mandatory )
                dfault = par->last;
            }
            cmd->argv[i] = strdup( (dfault != NULL) ? dfault : "" );
          }
          else
            cmd->argline = mstrcat( cmd->argline, cmd->argv[i] );
        }
        break;                  /* while(1) */
      }
    }
  }

  /* check that all mandatory parameters are there */
  if( cmd->argc < cmd->mandatory ) {
    if( cmd->argc == 0 ) {
      cmd->argv = (char**)malloc( cmd->mandatory * sizeof(char*) );
      cmd->argline = strdup( "" );
      cmd->argoffs = (int*)malloc( cmd->mandatory * sizeof(int) );
      cmd->argoffs[0] = 0;
    }
    else {
      cmd->argv = (char**)realloc( (char*)cmd->argv,
                                  cmd->mandatory * sizeof(char*) );
      cmd->argoffs = (int*)realloc( (char*)cmd->argoffs,
                                   cmd->mandatory * sizeof(int) );
    }
    for( i = cmd->argc; i < cmd->mandatory; i++ ) {
      cmd->argv[i] = NULL;
    }
    for( i = cmd->argc + 1; i < cmd->mandatory; i++ ) {
      cmd->argoffs[i] = cmd->argoffs[i-1];
    }
  }

  for( i = 0; i < cmd->mandatory; i++ ) {
    if( i >= cmd->argc || cmd->argv[i] == NULL ) {
      if( want_prompt ) {
        if( display_panel == NULL ) {
          /* prompt on command line */
          cmd->argv[i] = strdup( prompt_argument( cmd->par[i] ) );
          if( cmd->argline[0] != '\0' )
            cmd->argline = mstrcat( cmd->argline, " " );
          cmd->argoffs[i] = strlen( cmd->argline );
          cmd->argline = mstrcat( cmd->argline, cmd->argv[i] );
          cmd->argc++;
        }
        else {
          /* display panel */
          return (*display_panel)( cmd, i, block_panel );
        }
      }
      else {
        ku_alfa();
        printf( " *** Mandatory parameter %s missing\n", cmd->par[i]->name );
        return 1;
      }
    }
  }

  if( macro_args != NULL ) {
    /* insert macro arguments for EXEC command */
    cmd->argv = (char**)realloc( (char*)cmd->argv,
                                (cmd->argc+1) * sizeof(char*) );
    cmd->argoffs = (int*)realloc( (char*)cmd->argoffs,
                                 (cmd->argc+1) * sizeof(int) );
    cmd->argline = mstrcat( cmd->argline, " " );
    cmd->argoffs[cmd->argc] = strlen( cmd->argline );
    cmd->argv[cmd->argc] = strdup( macro_args );
    cmd->argline = mstrcat( cmd->argline, macro_args );
    cmd->argc++;
  }

#ifdef DBMALLOC
  if( cmd->argc > 0 ) {
    malloc_mark( cmd->argv );
    malloc_mark( cmd->argoffs );
    malloc_mark( cmd->argline );
    for( i = 0; i < cmd->argc; i++ ) {
      malloc_mark( cmd->argv[i] );
    }
  }
#endif

  if( want_prompt < 0 && display_panel != NULL ) {
    /* force panel display */
    return (*display_panel)( cmd, i, block_panel );
  }
  return exec_decoded_cmd( cmd );
}


/*
 * execute the command
 */
int exec_decoded_cmd( KmCommand *cmd )
{
  int status;
  int do_timing = (kc_flags.timing == KmTIMING_ALL
                   || ( kc_flags.timing == KmTIMING_ON
                       && !kc_flags.in_macro ));
  clock_t user_time = clock();
  time_t  real_time = time( NULL );
  int i;

  /* save current values in last */
  for( i = 0; i < cmd->total; i++ ) {
    KmParameter *par = cmd->par[i];
    if( i >= cmd->mandatory && par->dfault == NULL ) {
      /*
       * optional parameters without default:
       * overwrite last value by command line argument
       * otherwise keep value from prompt in next_argument()
       */
      if( i < cmd->argc ) {
        if( par->last != NULL )
          free( par->last );
        par->last = strdup( cmd->argv[i] );
#ifdef DBMALLOC
        malloc_mark( par->last );
#endif
      }
    }
    else {
      if( par->last != NULL )
        free( par->last );
      if( i < cmd->argc ) {
        par->last = strdup( cmd->argv[i] );
#ifdef DBMALLOC
        malloc_mark( par->last );
#endif
      }
      else {
        par->last = NULL;
      }
    }
  }

  curr_cmd = cmd;
  if( cmd->list_par > 0 && cmd->list_par <= cmd->argc ) {
    /* execute command for each value in a comma separated list */
    while( 1 ) {
      char *p;
      int paren = 0;
      int comma = 0;
      for( p = cmd->argv[cmd->list_par-1]; *p != '\0'; p++ ) {
        /* look for comma outside balanced parentheses */
        if( *p == '(' )
          paren++;
        else if( *p == ')' )
          paren--;
        else if( paren == 0 && *p == ',' ) {
          comma = 1;
          *p++ = '\0';
          break;
        }
      }
      status = call_action( cmd );
      /*  if( kc_flags.quit_macro ) */
      if( kc_flags.in_macro && kc_flags.quit_macro )
        status = KUMAC_UNWIND; /* quit macro execution */
      if( !comma || status != 0 )
        break;
      strcpy( cmd->argv[cmd->list_par-1], p );
    }
  }
  else {
    /* command has no parameter of list type */
    status = call_action( cmd );
  }
  if( do_timing )
    ku_time( real_time, user_time );

  return status;
}


/*
 * print real and CPU time
 */
void ku_time( time_t real_time,
             clock_t user_time )
{
  double cptime = (double)( clock() - user_time ) / CLOCKS_PER_SEC;
  double rtime = difftime( time( NULL ), real_time ) + 0.5;
  int seconds;
  int minutes;
  int hours;

  hours = rtime / 3600;
  rtime -= hours * 3600;
  minutes = rtime / 60;
  rtime -= minutes * 60;
  seconds = rtime;

  ku_alfa();
  printf( " Real time %s:%s:%s, CP time %.3f\n",
         strfromi( hours, 2 ), strfromi( minutes, 2 ), strfromi( seconds, 2 ),
         cptime );
}


/*
 * return difference in real time since last call in format hh:mm:ss
 */
void F77_ENTRY_C(Kirtim,chtime)
/* { */
  static int initialized = 0;
  static time_t time0;
  time_t time1 = time( NULL );
  double rtime;
  int seconds;
  int minutes;
  int hours;
  char timebuf[9];

  if( !initialized ) {
    initialized = 1;
    time0 = time( NULL );
  }
  rtime = difftime( time1, time0 );
  time0 = time1;
  rtime += 0.5;
  hours = rtime / 3600;
  rtime -= hours * 3600;
  minutes = rtime / 60;
  rtime -= minutes * 60;
  seconds = rtime;

  strcpy( timebuf, "00:00:00" );
  if( hours < 100 ) {
    timebuf[0] += hours / 10;
    timebuf[1] += hours % 10;
  }
  else {
    timebuf[0] = '*';
    timebuf[1] = '*';
  }
  timebuf[3] += minutes / 10;
  timebuf[4] += minutes % 10;
  timebuf[6] += seconds / 10;
  timebuf[7] += seconds % 10;

  fstrset( chtime, len_chtime, timebuf );
}


static  time_t last_real_time;
static clock_t last_user_time;

/*
 * print timing since last call to KUTIME/KUTIM0
 */
void F77_ENTRY(Kutime)
/* { */
  ku_time( last_real_time, last_user_time );
  last_real_time = time( NULL );
  last_user_time = clock();
}


/*
 * reset timing
 */
void F77_ENTRY(Kutim0)
/* { */
  last_real_time = time( NULL );
  last_user_time = clock();
}


/*
 * call the action routine
 */
static
int do_action( cmd )
     KmCommand *cmd;
{
#ifdef DBMALLOC
  unsigned long histid1, histid2, orig_size, current_size;

  orig_size = malloc_size( &histid1 );
#endif

#ifdef vms
  if( kc_flags.action_nesting == 0 && kc_flags.in_motif )
    VAXC$ESTABLISH( signal_handler );
#endif

  ku_intr( 1 );                 /* allow interrupts */

  kc_flags.action_nesting++;
  if( cmd->action_F != NULL ) {
    IQUEST(1) = 0;
    (*cmd->action_F)();
  }
  else if( cmd->action_C != NULL ) {
    IQUEST(1) = (*cmd->action_C)();
  }
  kc_flags.action_nesting--;

#ifdef DBMALLOC
  current_size = malloc_size( &histid2 );

  if( current_size != orig_size ) {
    /* check for memory leaks in action routine */
    malloc_list( 2, histid1, histid2 );
  }
#endif

  return 1;
}


static
int call_action( KmCommand *cmd )
{
  int status;

  if( kc_flags.in_macro && kc_flags.trace_test )
    return 0;                   /* TRACE ON TEST */

  if( kjmpaddr.disp_flush_C != NULL )
    (*kjmpaddr.disp_flush_C)(); /* eat up queued Motif events */

  if( cmd->action_F == NULL && cmd->action_C == NULL ) {
    /* no action routine -- execute HELP path */
    char *line = str3dup( "/KUIP/HELP '", cmd->path, "'" );
    status = ku_exel( line );
    free( line );
    return status;
  }

  next_arg = 0;
  if( do_action( cmd ) == 1 )
    status = IQUEST(1);
  else {
    /* this branch only taken for VMS */
    reset_break();
    status = KUMAC_UNWIND;
  }

#ifdef vms
  /*
   * On VMS we are not allowed to break and reenter the Motif mainloop.
   * Therefore we allow ^C only to be delivered while being inside the
   * action routine.
   */
  if( kc_flags.in_motif )
    ku_intr( 0 );               /* disable interrupts */
#endif

  if( kc_break.error_msg != NULL ) {
    /*
     * print the runtime message stored by the error handler
     * in order to avoid recursive I/O
     */
    ku_alfa();
    puts( kc_break.error_msg );
    free( kc_break.error_msg );
    kc_break.error_msg = NULL;
  }

  /* increment execution counter */
  if( kc_flags.in_macro )
    cmd->xcount += XCOUNT_OFFSET;
  else if( (cmd->xcount & XCOUNT_MASK) < XCOUNT_MASK )
    cmd->xcount += 1;

  if( kc_break.soft_intr ) {
    kc_break.soft_intr = 0;
    status = KUMAC_UNWIND;
  }

  return status;
}


/*
 * return statistic about command execution:
 *   IWHICH == 0 : number of commands in total (macro + keyboard)
 *   IWHICH == 1 : number of different commands in total
 *   IWHICH == 2 : number of commands from keyboard
 *   IWHICH == 3 : number of different commands from keyboard
 *   IWHICH == 4 : number of commands inside macro
 *   IWHICH == 5 : number of different commands inside macro
 *   otherwise reset counters
 */
INTEGER F77_ENTRY_I(Kustat,iwhich )
/* { */
  KmMenu **menu_list = root_menu_list( "" );
  KmMenu *menu;
  int which = *iwhich;
  INTEGER count = 0;
  int n;

  for( n = 0; (menu = menu_list[n]) != NULL; n++ ) {
    KmCommand *cmd;
    for( cmd = menu->cmds; cmd != NULL; cmd = cmd->next ) {
      switch( which ) {

      case 0:
        count += (cmd->xcount & XCOUNT_MASK) + (cmd->xcount >> XCOUNT_SHIFT);
        break;

      case 1:
        if( cmd->xcount != 0 )
          count++;
        break;

      case 2:
        count += (cmd->xcount & XCOUNT_MASK);
        break;

      case 3:
        if( (cmd->xcount & XCOUNT_MASK) != 0 )
          count++;
        break;

      case 4:
        count += (cmd->xcount >> XCOUNT_SHIFT);
        break;

      case 5:
        if( (cmd->xcount >> XCOUNT_SHIFT) != 0 )
          count++;
        break;

      default:                  /* reset counters */
        cmd->xcount = 0;
      }
    }
  }
  free( menu_list );
  return count;
}


/*
 * prompt for missing argument
 */
static
const char *prompt_argument( KmParameter *par )
{
  char *dfault;

  if( par->last != NULL )
    dfault = par->last;
  else if( par->dfault != NULL )
    dfault = par->dfault;
  else
    dfault = "";

  return ku_pros( par->prompt, dfault );
}


/*
 * chop off the line at the first unquoted semicolon
 * ignore semicolons followed by something which could be a VMS version number
 * return the remaining line or pointer to '\0'
 */
char *split_at_semicolon( char *line )
{
  int quoted = 0;
  char *tail = "";
  char *p = line;

  while( *p != '\0' ) {

    if( *p == '\'' ) {
      quoted = !quoted;
    }
    else if( *p == ';' && !quoted
            && ( p[1] == '\0'
                || strchr( "0123456789+-?[*", p[1] ) == NULL ) ) {

      tail = p + 1;
      while( *tail == ' ' ) {
        tail++;
      }
      break;
    }
    p++;
  }

  while( p > line && p[-1] == ' ' )
    p--;

  *p = '\0';

  return tail;
}


/*
 * execute command with echo
 */
int ku_exec( const char *cmd_string )
{
  if( kc_flags.echo_command > 0 ) {
    ku_alfa();
    printf( "%s> %s\n", kc_flags.echo_prompt, cmd_string );
  }

  return ku_exel( cmd_string );
}


int ku_exel( const char *cmd_string )
{
  return exec_cmd_string( cmd_string, 1, kjmpaddr.disp_panel_C );
}


void F77_ENTRY_C(Kcexec,chline)
/* { */
  char *cmd_string = fstr0dup( chline, len_chline );
  if( cmd_string != NULL ) {
    ku_exel( cmd_string );
    free( cmd_string );
  }
}


INTEGER F77_ENTRY_C(Kuexec,chline)
/* { */
  INTEGER status = 0;
  char *cmd_string = fstr0dup( chline, len_chline );
  if( cmd_string != NULL ) {
    status = ku_exec( cmd_string );
    free( cmd_string );
  }
  return status;
}


INTEGER F77_ENTRY_C(Kuexel,chline)
/* { */
  INTEGER status = 0;
  char *cmd_string = fstr0dup( chline, len_chline );
  if( cmd_string != NULL ) {
    status = ku_exel( cmd_string );
    free( cmd_string );
  }
  return status;
}


/*
 * return the next argument
 */
static
char *next_value( const char *called_by )
{
  char *value = find_value( NULL, NULL, NULL );

  if( value == NULL ) {
    ku_alfa();
    printf( " *** Command %s requests undefined parameter #%d with %s\n",
           curr_cmd->path, next_arg + 1, called_by );
    value = "";
  }
  else {
    int i;
    for( i = strlen( value ); i > 0; i-- ) {
      if( value[i-1] != ' ' )
        break;
    }
    if( i == 0 )                /* return empty string if all blanks */
      value = "";
  }

  next_arg++;
  return value;
}


/*
 * find the value to return to the next KUGETx call:
 *  ---- command line argument
 *   or  default value
 *   or  last value for optional parameters without default
 * if one of the input pointers is defined store it as last value
 */
static
char *find_value( char *cval,
                 INTEGER *ival,
                 REAL *rval )
{
  char *value = NULL;

  if( next_arg < curr_cmd->argc )
    value = curr_cmd->argv[next_arg];

  if( value == NULL && next_arg < curr_cmd->total ) {
    KmParameter *par = curr_cmd->par[next_arg];
    value = par->dfault;

    /* set last value for optional parameters without default */
    if( value == NULL && next_arg >= curr_cmd->mandatory ) {

      if( cval != NULL )
        value = cval;
      else if( ival != NULL )
        value = strfromi( (int)*ival, 0 );
      else if( rval != NULL )
        value = strfromd9( (double)*rval, 0 );

      if( value != NULL ) {
        if( par->last != NULL )
          free( par->last );
        par->last = strdup( value );
      }

      value = par->last;
    }
  }
  return value;
}


void ku_spy( const char *flag )
{
  static int save_next_arg;

  if( strcasecmp( flag, "ON" ) == 0 ) {
    save_next_arg = next_arg;
  }
  else if( strcasecmp( flag, "OFF" ) == 0 ) {
    if( next_arg > save_next_arg )
      next_arg = save_next_arg;
  }
}


void F77_ENTRY_C(Kuspy,chopt)
/* { */
  char *opt = fstrdup( chopt, len_chopt );
  ku_spy( opt );
  free( opt );
}


/*
 * generic KUGETx for character arguments
 */
static
int get_chval( char *chval,
              int len,
              char *(*getx)() )
{
  char *cval = fstrdup( chval, len );
  find_value( cval, NULL, NULL );
  free( cval );
  return fstrset( chval, len, (*getx)() );
}


/*
 * get argument in uppercase
 */
char *ku_getc()
{
  char *value = next_value( "KUGETC" );
  strupper( value );
  if( list_arg != NULL )
    free( list_arg );
  list_arg = str0dup( value );

#ifdef DBMALLOC
  if( list_arg != NULL )
    malloc_mark( list_arg );
#endif

  return value;
}

void F77_ENTRY_CI(Kugetc,chval,length)
/* { */
  *length = get_chval( chval, len_chval, ku_getc );
}


/*
 * get all arguments to end of line
 */
char *ku_gete()
{
  if( next_arg < curr_cmd->argc )
    return curr_cmd->argline + curr_cmd->argoffs[next_arg];
  return next_value( "KUGETE" );
}

void F77_ENTRY_CI(Kugete,chpar,length)
/* { */
  *length = get_chval( chpar, len_chpar, ku_gete );
}


/*
 * get argument with FILECASE conversion
 */
char *ku_getf()
{
  char *value = next_value( "KUGETF" );
  ku_fcase( value );
  if( list_arg != NULL )
    free( list_arg );
  list_arg = str0dup( value );

#ifdef DBMALLOC
  if( list_arg != NULL )
    malloc_mark( list_arg );
#endif

  return value;
}

void F77_ENTRY_CI(Kugetf,chpar,length)
/* { */
  *length = get_chval( chpar, len_chpar, ku_getf );
}


/*
 * get argument without case conversion
 */
char *ku_gets()
{
  char *value = next_value( "KUGETS" );
  if( list_arg != NULL )
    free( list_arg );
  list_arg = str0dup( value );

#ifdef DBMALLOC
  if( list_arg != NULL )
    malloc_mark( list_arg );
#endif

  return value;
}

void F77_ENTRY_CI(Kugets,chpar,length)
/* { */
  *length = get_chval( chpar, len_chpar, ku_gets );
}


/*
 * retrieve the next item from the list loaded with KUGETC or KUGETS
 */
char *ku_getl()
{
  static char *list_this = NULL;

  if( list_this != NULL ) {
    free( list_this );
    list_this = NULL;
  }

  if( list_arg != NULL ) {
    char *p;
    int quoted = 0;
    int paren = 0;
    int comma = 0;

    for( p = list_arg; *p != '\0'; p++ ) {
      /* look for separator outside balanced parentheses or quotes */
      /* \n is used to protect commas in VARARG lists */
      if( *p == '\n' )
        *p = ',';
      else if( *p == '\'' )
        quoted = !quoted;
      else if( !quoted ) {
        if( *p == '(' )
          paren++;
        else if( *p == ')' )
          paren--;
        else if( paren == 0 && *p == ',' ) {
          comma = 1;
          *p++ = '\0';
          break;
        }
      }
    }

    if( comma ) {
      /* pass the first item as argument and remove it from the rest */
      list_this = strdup( list_arg );
      while( *p == ' ' )
        p++;                    /* skip leading blanks */
	    memmove( list_arg, p, strlen(p) + 1 );
/*      strcpy( list_arg, p );*/
    }
    else {
      /* pass the complete thing */
      list_this = strdup( list_arg );
      free( list_arg );
      list_arg = NULL;
    }
#ifdef DBMALLOC
    malloc_mark( list_this );
#endif
  }

  return list_this;
}

void F77_ENTRY_CI(Kugetl,chpar,length)
/* { */
  *length = get_chval( chpar, len_chpar, ku_getl );
}


/*
 * get integer argument
 */
int ku_geti()
{
  char *value = strdup( next_value( "KUGETI" ) );
  KmParameter *par = (next_arg > curr_cmd->total)
    ? NULL : curr_cmd->par[next_arg-1];

  while( 1 ) {
    int do_prompt = 0;
    int ival = 0;
    double dval;

    if( ku_math( value, &dval ) == 0 ) {
      ival = dval;

      if( par != NULL ) {
        /* check against parameter definition */
        if( par->type != KmTYPE_INT ) {
          printf( " *** %s: KUGETI called for non-I parameter %s\n",
                 curr_cmd->path, par->name );
        }
        else {
          KmParInt *pnum = (KmParInt*)par->ptype;

          if( pnum->range_lower != NULL && ival < atoi( pnum->range_lower ) ) {
            printf( " *** %s: %s=%d less than lower range value %s\n",
                   curr_cmd->path, par->name, ival, pnum->range_lower );
            do_prompt = 1;
          }
          if( pnum->range_upper != NULL && ival > atoi( pnum->range_upper ) ) {
            printf( " *** %s: %s=%d greater than upper range value %s\n",
                   curr_cmd->path, par->name, ival, pnum->range_upper );
            do_prompt = 1;
          }
          if( par->range_count > 0 ) {
            int found = 0;
            int i;
            for( i = 0; i < par->range_count; i++ ) {
              if( ival == atoi( par->range_value[i] ) ) {
                found =1;
                break;
              }
            }
            if( !found ) {
              printf( " *** %s: %s=%d not in list of allowed values\n",
                   curr_cmd->path, par->name, ival );
              do_prompt = 1;
            }
          }
        }
      }
    }
    else {
      printf( " *** %s: invalid integer number '%s'\n",
             curr_cmd->path, value );
      do_prompt = 1;
    }

    if( do_prompt ) {
      const char *p = prompt_argument( par );
      free( value );
      if( p == NULL )
        return 0;
      value = strdup( p );
    }
    else {
      free( value );
      return ival;
    }
  }
}

void F77_ENTRY_I(Kugeti,ipar)
/* { */
  find_value( NULL, ipar, NULL );
  *ipar = ku_geti();
}


/*
 * get real argument
 */
double ku_getr()
{
  char *value = strdup( next_value( "KUGETR" ) );
  KmParameter *par = (next_arg > curr_cmd->total)
    ? NULL : curr_cmd->par[next_arg-1];

  while( 1 ) {
    int do_prompt = 0;
    double rval;

    if( ku_math( value, &rval ) == 0 ) {

      if( par != NULL ) {
        /* check against parameter definition */
        if( par->type != KmTYPE_REAL ) {
          printf( " *** %s: KUGETR called for non-R parameter %s\n",
                 curr_cmd->path, par->name );
        }
        else {
          KmParReal *pnum = (KmParReal*)par->ptype;

          if( pnum->range_lower != NULL && rval < atof( pnum->range_lower ) ) {
            printf( " *** %s: %s=%f less than lower range value %s\n",
                   curr_cmd->path, par->name, rval, pnum->range_lower );
            do_prompt = 1;
          }
          if( pnum->range_upper != NULL && rval > atof( pnum->range_upper ) ) {
            printf( " *** %s: %s=%f greater than upper range value %s\n",
                   curr_cmd->path, par->name, rval, pnum->range_upper );
            do_prompt = 1;
          }
          if( par->range_count > 0 ) {
            int found = 0;
            int i;
            for( i = 0; i < par->range_count; i++ ) {
              if( rval == atof( par->range_value[i] ) ) {
                found =1;
                break;
              }
            }
            if( !found ) {
              printf( " *** %s: %s=%f not in list of allowed values\n",
                   curr_cmd->path, par->name, rval );
              do_prompt = 1;
            }
          }
        }
      }
    }
    else {
      printf( " *** %s: invalid real number '%s'\n",
             curr_cmd->path, value );
      do_prompt = 1;
    }

    if( do_prompt ) {
      const char *p = prompt_argument( par );
      free( value );
      if( p == NULL )
        return 0;
      value = strdup( p );
    }
    else {
      free( value );
      return rval;
    }
  }
}

void F77_ENTRY_R(Kugetr,rpar)
/* { */
  find_value( NULL, NULL, rpar );
  *rpar = ku_getr();
}


/*
 * return number of arguments in command line
 */
int ku_npar()
{
  return curr_cmd->argc;
}

INTEGER F77_ENTRY(Kunpar)
/* { */
  return ku_npar();
}


/*
 * return command name and number of arguments
 */
void F77_ENTRY_CI(Kupatl,chpath,npar)
/* { */
  fstrset( chpath, len_chpath, curr_cmd->name );
  *npar = ku_npar();
}


/*
 * return command path and number of arguments
 */
void F77_ENTRY_CII(Kupath,chpath,nlev,npar)
/* { */
  char *path = strdup( &curr_cmd->path[1] );
  char *p;

  *nlev = 0;
  for( p = strtok( path, "/" ); p != NULL; p = strtok( NULL, "/" ) ) {
    fstrset( chpath, len_chpath, p );
    chpath += len_chpath;
    *nlev += 1;
  }
  free( path );
  *npar = ku_npar();
}

char *ku_path()
{
  return curr_cmd->path;
}


