#ifndef lint
static char *RCSid = "$Id: command.c,v 1.20.2.3 2002/01/31 21:18:22 lhecking Exp $";
#endif

/* GNUPLOT - command.c */

/*[
 * Copyright 1986 - 1993, 1998   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

/*
 * Changes:
 * 
 * Feb 5, 1992  Jack Veenstra   (veenstra@cs.rochester.edu) Added support to
 * filter data values read from a file through a user-defined function before
 * plotting. The keyword "thru" was added to the "plot" command. Example
 * syntax: f(x) = x / 100 plot "test.data" thru f(x) This example divides all
 * the y values by 100 before plotting. The filter function processes the
 * data before any log-scaling occurs. This capability should be generalized
 * to filter x values as well and a similar feature should be added to the
 * "splot" command.
 * 
 * 19 September 1992  Lawrence Crowl  (crowl@cs.orst.edu)
 * Added user-specified bases for log scaling.
 */

#include "plot.h"
#include "setshow.h"
#include "fit.h"
#include "binary.h"

#if defined(MSDOS) || defined(DOS386)
# ifdef DJGPP
extern char HelpFile[];		/* patch for do_help  - AP */
# endif				/* DJGPP */
# ifdef __TURBOC__
#  ifndef _Windows
extern unsigned _stklen = 16394;	/* increase stack size */
extern char HelpFile[];		/* patch for do_help  - DJL */
#  endif /* _Windows */
# endif /* TURBOC */
#endif /* MSDOS */

#ifndef _Windows
# include "help.h"
#else
static int winsystem __PROTO((char *));
#endif /* _Windows */

#ifndef STDOUT
# define STDOUT 1
#endif

#ifdef _Windows
# include <windows.h>
# ifdef __MSC__
#  include <malloc.h>
# else
#  include <alloc.h>
#  include <dir.h>		/* setdisk() */
# endif				/* !MSC */
# include "win/wgnuplib.h"
extern TW textwin;
extern LPSTR winhelpname;
extern void screen_dump(void);	/* in term/win.trm */
extern int Pause(LPSTR mess);	/* in winmain.c */
#endif /* _Windows */

#ifdef _Windows
# define SET_CURSOR_WAIT SetCursor(LoadCursor((HINSTANCE) NULL, IDC_WAIT))
# define SET_CURSOR_ARROW SetCursor(LoadCursor((HINSTANCE) NULL, IDC_ARROW))
#else
# define SET_CURSOR_WAIT /* nought, zilch */
# define SET_CURSOR_ARROW /* nought, zilch */
#endif

#ifdef OS2
 /* emx has getcwd, chdir that can handle drive names */
# define chdir  _chdir2
extern int PM_pause(char *);  /* term/pm.trm */
extern int ExecuteMacro(char *, int); /* plot.c */
#endif /* OS2 */

#ifdef VMS
int vms_vkid;			/* Virtual keyboard id */
int vms_ktid;			/* key table id, for translating keystrokes */
#endif /* VMS */

/* Used by vws.trm */
void replotrequest __PROTO((void));


/* static prototypes */
static int command __PROTO((void));
static int read_line __PROTO((char *prompt));
static void do_shell __PROTO((void));
static void do_help __PROTO((int toplevel));
static void do_system __PROTO((void));
static int changedir __PROTO((char *path));

/* input data, parsing variables */
#ifdef AMIGA_SC_6_1
__far int num_tokens, c_token;
#else
int num_tokens, c_token;
#endif

struct lexical_unit *token;
int token_table_size;

char *input_line;
int input_line_len;
int inline_num;			/* input line number */

struct udft_entry *dummy_func;	/* NULL means no dummy vars active */

char c_dummy_var[MAX_NUM_VAR][MAX_ID_LEN+1];	/* current dummy vars */


/* support for replot command */
char *replot_line;
int plot_token;			/* start of 'plot' command */

/* If last plot was a 3d one. */
TBOOLEAN is_3d_plot = FALSE;

#define Inc_c_token if (++c_token >= num_tokens)	\
                        int_error ("Syntax error", c_token);

/* support for dynamic size of input line */
void extend_input_line()
{
    if (input_line_len == 0) {
	/* first time */
	input_line = gp_alloc(MAX_LINE_LEN, "input_line");
	input_line_len = MAX_LINE_LEN;
	input_line[0] = NUL;
    } else {
	input_line = gp_realloc(input_line, input_line_len + MAX_LINE_LEN, "extend input line");
	input_line_len += MAX_LINE_LEN;
	FPRINTF((stderr, "extending input line to %d chars\n", input_line_len));
    }
}


void extend_token_table()
{
    if (token_table_size == 0) {
	/* first time */
	token = (struct lexical_unit *) gp_alloc(MAX_TOKENS * sizeof(struct lexical_unit), "token table");
	token_table_size = MAX_TOKENS;
    } else {
	token = gp_realloc(token, (token_table_size + MAX_TOKENS) * sizeof(struct lexical_unit), "extend token table");
	token_table_size += MAX_TOKENS;
	FPRINTF((stderr, "extending token table to %d elements\n", token_table_size));
    }
}


void init_memory()
{
    extend_input_line();
    extend_token_table();
    replot_line = gp_alloc(1, "string");
    *replot_line = NUL;
}


int com_line()
{
    if (multiplot) {
	/* calls int_error() if it is not happy */
	term_check_multiplot_okay(interactive);

	if (read_line("multiplot> "))
	    return (1);
    } else {
	if (read_line(PROMPT))
	    return (1);
    }

    /* So we can flag any new output: if false at time of error,
     * we reprint the command line before printing caret.
     * TRUE for interactive terminals, since the command line is typed.
     * FALSE for non-terminal stdin, so command line is printed anyway.
     * (DFK 11/89)
     */
    screen_ok = interactive;

    if (do_line())
	return (1);
    else
	return (0);
}


int do_line()
{
    /* Line continuation has already been handled
     * by read_line() */
    char *inlptr = input_line;

    /* Skip leading whitespace */
    while (isspace((int)*inlptr))
	inlptr++;

    if (inlptr != input_line) {
	/* If there was leading whitespace, copy the actual
	 * command string to the front. use memmove() because
	 * source and target overlap */
	memmove(input_line,inlptr,strlen(inlptr));
	/* Terminate resulting string */
	input_line[strlen(inlptr)] = NUL;
    }

    FPRINTF((stderr, "Input line: \"%s\"\n",input_line));

    /* also used in load_file */
    if (is_system(input_line[0])) {
	do_system();
	if (interactive)	/* 3.5 did it unconditionally */
	    (void) fputs("!\n", stderr);	/* why do we need this ? */
	return (0);
    }
    num_tokens = scanner(&input_line, &input_line_len);
    c_token = 0;
    while (c_token < num_tokens) {
	if (command())
	    return (1);
	if (c_token < num_tokens) {	/* something after command */
	    if (equals(c_token, ";"))
		c_token++;
	    else
		int_error("';' expected", c_token);
	}
    }
    return (0);
}


void define()
{
    register int start_token;	/* the 1st token in the function definition */
    register struct udvt_entry *udv;
    register struct udft_entry *udf;

    if (equals(c_token + 1, "(")) {
	/* function ! */
	int dummy_num = 0;
	struct at_type *at_tmp;
	char save_dummy[MAX_NUM_VAR][MAX_ID_LEN + 1];
	memcpy(save_dummy, c_dummy_var, sizeof(save_dummy));
	start_token = c_token;
	do {
	    c_token += 2;	/* skip to the next dummy */
	    copy_str(c_dummy_var[dummy_num++], c_token, MAX_ID_LEN);
	} while (equals(c_token + 1, ",") && (dummy_num < MAX_NUM_VAR));
	if (equals(c_token + 1, ","))
	    int_error("function contains too many parameters", c_token + 2);
	c_token += 3;		/* skip (, dummy, ) and = */
	if (END_OF_COMMAND)
	    int_error("function definition expected", c_token);
	udf = dummy_func = add_udf(start_token);
	if ((at_tmp = perm_at()) == (struct at_type *) NULL)
	    int_error("not enough memory for function", start_token);
	if (udf->at)		/* already a dynamic a.t. there */
	    free((char *) udf->at);	/* so free it first */
	udf->at = at_tmp;	/* before re-assigning it. */
	memcpy(c_dummy_var, save_dummy, sizeof(save_dummy));
	m_capture(&(udf->definition), start_token, c_token - 1);
	dummy_func = NULL;	/* dont let anyone else use our workspace */
    } else {
	/* variable ! */
	start_token = c_token;
	c_token += 2;
	udv = add_udv(start_token);
	(void) const_express(&(udv->udv_value));
	udv->udv_undef = FALSE;
    }
}


static int command()
{
    FILE *fp;
    int i;
    /* string holding name of save or load file */
    char sv_file[MAX_LINE_LEN + 1];

    for (i = 0; i < MAX_NUM_VAR; i++)
	c_dummy_var[i][0] = NUL;	/* no dummy variables */

    if (is_definition(c_token))
	define();
    else if (almost_equals(c_token, "h$elp") || equals(c_token, "?")) {
	c_token++;
	do_help(1);
    } else if (equals(c_token, "testtime")) {
	/* given a format and a time string, exercise the time code */
	char format[160], string[160];
	struct tm tm;
	double secs;
	if (isstring(++c_token)) {
	    quote_str(format, c_token, 159);
	    if (isstring(++c_token)) {
		quote_str(string, c_token++, 159);
		memset(&tm, 0, sizeof(tm));
		gstrptime(string, format, &tm);
		secs = gtimegm(&tm);
		fprintf(stderr, "internal = %f - %d/%d/%d::%d:%d:%d , wday=%d, yday=%d\n",
			secs, tm.tm_mday, tm.tm_mon + 1, tm.tm_year % 100, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday, tm.tm_yday);
		memset(&tm, 0, sizeof(tm));
		ggmtime(&tm, secs);
		gstrftime(string, 159, format, secs);
		fprintf(stderr, "convert back \"%s\" - %d/%d/%d::%d:%d:%d , wday=%d, yday=%d\n",
			string, tm.tm_mday, tm.tm_mon + 1, tm.tm_year % 100, tm.tm_hour, tm.tm_min, tm.tm_sec, tm.tm_wday, tm.tm_yday);
	    }
	}
    } else if (almost_equals(c_token, "test")) {
	c_token++;
	test_term();
    } else if (almost_equals(c_token, "scr$eendump")) {
	c_token++;
#ifdef _Windows
	screen_dump();
#else
	fputs("screendump not implemented\n", stderr);
#endif
    } else if (almost_equals(c_token, "pa$use")) {
	struct value a;
	int sleep_time, text = 0;
	char buf[MAX_LINE_LEN + 1];

	c_token++;
	sleep_time = (int) real(const_express(&a));
	buf[0] = NUL;
	if (!(END_OF_COMMAND)) {
	    if (!isstring(c_token))
		int_error("expecting string", c_token);
	    else {
		quote_str(buf, c_token, MAX_LINE_LEN);
		++c_token;
#ifdef _Windows
		if (sleep_time >= 0)
#else
# ifdef OS2
		if (strcmp(term->name, "pm") != 0 || sleep_time >= 0)
# else
#  ifdef MTOS
	        if (strcmp(term->name, "mtos") != 0 || sleep_time >= 0)
#  endif /* MTOS */
# endif /* OS2 */
#endif /* _Windows */
			fputs(buf, stderr);
		text = 1;
	    }
	}
	if (sleep_time < 0) {
#ifdef _Windows
	    if (!Pause(buf))
		bail_to_command_line();
#else
# ifdef OS2
	    if (strcmp(term->name, "pm") == 0 && sleep_time < 0) {
		int rc;
		if ((rc = PM_pause(buf)) == 0)
		    bail_to_command_line();
		else if (rc == 2) {
		    fputs(buf, stderr);
		    text = 1;
		    (void) fgets(buf, MAX_LINE_LEN, stdin);
		}
	    }
# else				/* !OS2 */
#  ifdef _Macintosh
	    if (strcmp(term->name, "macintosh") == 0 && sleep_time < 0)
		Pause(sleep_time);
#  else				/* !_Macintosh */
#   ifdef MTOS
	    if (strcmp(term->name, "mtos") == 0) {
		int MTOS_pause(char *buf);
		int rc;
		if ((rc = MTOS_pause(buf)) == 0)
		    bail_to_command_line();
		else if (rc == 2) {
		    fputs(buf, stderr);
		    text = 1;
		    (void) fgets(buf, MAX_LINE_LEN, stdin);
		}
	    } else if (strcmp(term->name, "atari") == 0) {
		char *readline(char *);
		char *line = readline("");
		if (line)
		    free(line);
	    } else
		(void) fgets(buf, MAX_LINE_LEN, stdin);
#   else			/* !MTOS */
#    ifdef ATARI
	    if (strcmp(term->name, "atari") == 0) {
		char *readline(char *);
		char *line = readline("");
		if (line)
		    free(line);
	    } else
		(void) fgets(buf, MAX_LINE_LEN, stdin);
#    else			/* !ATARI */
	    (void) fgets(buf, MAX_LINE_LEN, stdin);
	    /* Hold until CR hit. */
#    endif			/* !ATARI */
#   endif			/* !MTOS */
#  endif			/* !_Macintosh */
# endif				/* !OS2 */
#endif
	}
	if (sleep_time > 0)
	    GP_SLEEP(sleep_time);

	if (text != 0 && sleep_time >= 0)
	    fputc('\n', stderr);
	screen_ok = FALSE;
    } else if (almost_equals(c_token, "pr$int")) {
	int need_space = 0;	/* space printed between two expressions only */
	screen_ok = FALSE;
	do {
	    ++c_token;
	    if (isstring(c_token)) {
		char s[MAX_LINE_LEN];
		quote_str(s, c_token, MAX_LINE_LEN);
		fputs(s, stderr);
		need_space = 0;
		++c_token;
	    } else {
		struct value a;
		(void) const_express(&a);
		if (need_space)
		    putc(' ', stderr);
		need_space = 1;
		disp_value(stderr, &a);
	    }
	} while (!END_OF_COMMAND && equals(c_token, ","));

	(void) putc('\n', stderr);
    } else if (almost_equals(c_token, "fit")) {
	++c_token;
	do_fit();
    } else if (almost_equals(c_token, "up$date")) {
	char tmps[80];
	char tmps2[80];
	/* Have to initialise tmps2, otherwise
	 * update() cannot decide whether a valid
	 * filename was given. lh
	 */
	tmps2[0] = NUL;
	if (!isstring(++c_token))
	    int_error("Parameter filename expected", c_token);
	quote_str(tmps, c_token++, 80);
	if (!(END_OF_COMMAND)) {
	    if (!isstring(c_token))
		int_error("New parameter filename expected", c_token);
	    else
		quote_str(tmps2, c_token++, 80);
	}
	update(tmps, tmps2);
    } else if (almost_equals(c_token, "p$lot")) {
	plot_token = c_token++;
	SET_CURSOR_WAIT;
	plotrequest();
	SET_CURSOR_ARROW;
    } else if (almost_equals(c_token, "sp$lot")) {
	plot_token = c_token++;
	SET_CURSOR_WAIT;
	plot3drequest();
	SET_CURSOR_ARROW;
    } else if (almost_equals(c_token, "rep$lot")) {
	if (replot_line[0] == NUL)
	    int_error("no previous plot", c_token);
	c_token++;
	SET_CURSOR_WAIT;
	replotrequest();
	SET_CURSOR_ARROW;
    } else if (almost_equals(c_token, "se$t"))
	set_command();
    else if (almost_equals(c_token, "res$et"))
	reset_command();
    else if (almost_equals(c_token, "sh$ow"))
	show_command();
    else if (almost_equals(c_token, "cl$ear")) {
	term_start_plot();

	if (multiplot && term->fillbox) {
	    unsigned int x1 = (unsigned int) (xoffset * term->xmax);
	    unsigned int y1 = (unsigned int) (yoffset * term->ymax);
	    unsigned int width = (unsigned int) (xsize * term->xmax);
	    unsigned int height = (unsigned int) (ysize * term->ymax);
	    (*term->fillbox) (0, x1, y1, width, height);
	}
	term_end_plot();

	screen_ok = FALSE;
	c_token++;
    } else if (almost_equals(c_token, "she$ll")) {
	do_shell();
	screen_ok = FALSE;
	c_token++;
    } else if (almost_equals(c_token, "sa$ve")) {
	if (almost_equals(++c_token, "f$unctions")) {
	    if (!isstring(++c_token))
		int_error("expecting filename", c_token);
	    else {
		quote_str(sv_file, c_token, MAX_LINE_LEN);
		save_functions(fopen(sv_file, "w"));
	    }
	} else if (almost_equals(c_token, "v$ariables")) {
	    if (!isstring(++c_token))
		int_error("expecting filename", c_token);
	    else {
		quote_str(sv_file, c_token, MAX_LINE_LEN);
		save_variables(fopen(sv_file, "w"));
	    }
	} else if (almost_equals(c_token, "s$et")) {
	    if (!isstring(++c_token))
		int_error("expecting filename", c_token);
	    else {
		quote_str(sv_file, c_token, MAX_LINE_LEN);
		save_set(fopen(sv_file, "w"));
	    }
	} else if (isstring(c_token)) {
	    quote_str(sv_file, c_token, MAX_LINE_LEN);
	    save_all(fopen(sv_file, "w"));
	} else {
	    int_error("filename or keyword 'functions', 'variables', or 'set' expected", c_token);
	}
	c_token++;
    } else if (almost_equals(c_token, "l$oad")) {
	if (!isstring(++c_token))
	    int_error("expecting filename", c_token);
	else {
	    quote_str(sv_file, c_token, MAX_LINE_LEN);
	    /* load_file(fp=fopen(sv_file, "r"), sv_file, FALSE); OLD
	     * DBT 10/6/98 handle stdin as special case
	     * passes it on to load_file() so that it gets
	     * pushed on the stack and recusion will work, etc
	     */
	    fp = strcmp(sv_file, "-") ? fopen(sv_file, "r") : stdin; 
	    load_file(fp, sv_file, FALSE);
	    /* input_line[] and token[] now destroyed! */
	    c_token = num_tokens = 0;
	}
    } else if (almost_equals(c_token, "ca$ll")) {
	if (!isstring(++c_token))
	    int_error("expecting filename", c_token);
	else {
	    quote_str(sv_file, c_token, MAX_LINE_LEN);
	    load_file(fopen(sv_file, "r"), sv_file, TRUE);	/* Argument list follows filename */
	    /* input_line[] and token[] now destroyed! */
	    c_token = num_tokens = 0;
	}
    } else if (almost_equals(c_token, "if")) {
	double exprval;
	struct value t;
	if (!equals(++c_token, "("))	/* no expression */
	    int_error("expecting (expression)", c_token);
	exprval = real(const_express(&t));
	if (exprval != 0.0) {
	    /* fake the condition of a ';' between commands */
	    int eolpos = token[num_tokens - 1].start_index + token[num_tokens - 1].length;
	    --c_token;
	    token[c_token].length = 1;
	    token[c_token].start_index = eolpos + 2;
	    input_line[eolpos + 2] = ';';
	    input_line[eolpos + 3] = NUL;
	} else
	    c_token = num_tokens = 0;
    } else if (almost_equals(c_token, "rer$ead")) {
	fp = lf_top();
	if (fp != (FILE *) NULL)
	    rewind(fp);
	c_token++;
    } else if (almost_equals(c_token, "cd")) {
	if (!isstring(++c_token))
	    int_error("expecting directory name", c_token);
	else {
	    quote_str(sv_file, c_token, MAX_LINE_LEN);
	    if (changedir(sv_file)) {
		int_error("Can't change to this directory", c_token);
	    }
	    c_token++;
	}
    } else if (almost_equals(c_token, "pwd")) {
	GP_GETCWD(sv_file, sizeof(sv_file));
	fprintf(stderr, "%s\n", sv_file);
	c_token++;
    } else if (almost_equals(c_token, "ex$it") ||
	       almost_equals(c_token, "q$uit")) {
	/* graphics will be tidied up in main */
	return (1);
    } else if (!equals(c_token, ";")) {		/* null statement */
#ifdef OS2
	if (_osmode == OS2_MODE) {
	    if (token[c_token].is_token) {
		int rc;
		rc = ExecuteMacro(input_line + token[c_token].start_index,
				  token[c_token].length);
		if (rc == 0) {
		    c_token = num_tokens = 0;
		    return (0);
		}
	    }
	}
#endif
	int_error("invalid command", c_token);
    }
    return (0);
}


void done(status)
int status;
{
    term_reset();
    exit(status);
}

static int changedir(path)
char *path;
{
#if defined(MSDOS) || defined(WIN16) || defined(ATARI) || defined(DOS386)
# if defined(__ZTC__)
    unsigned dummy;		/* it's a parameter needed for dos_setdrive */
# endif

    /* first deal with drive letter */

    if (isalpha(path[0]) && (path[1] == ':')) {
	int driveno = toupper(path[0]) - 'A';	/* 0=A, 1=B, ... */

# if defined(ATARI)
	(void) Dsetdrv(driveno);
# endif

# if defined(__ZTC__)
	(void) dos_setdrive(driveno + 1, &dummy);
# endif

# if (defined(MSDOS) && defined(__EMX__)) || defined(__MSC__)
	(void) _chdrive(driveno + 1);
# endif


/* HBB: recent versions of DJGPP also have setdisk():,
 * so I del'ed the special code */
# if ((defined(MSDOS) || defined(_Windows)) && defined(__TURBOC__)) || defined(DJGPP)
	(void) setdisk(driveno);
# endif
	path += 2;		/* move past drive letter */
    }
    /* then change to actual directory */
    if (*path)
	if (chdir(path))
	    return 1;

    return 0;			/* should report error with setdrive also */

#elif defined(WIN32)
    return !(SetCurrentDirectory(path));
#else
    return chdir(path);
#endif /* MSDOS, ATARI etc. */
}


void replotrequest()
{
    if (equals(c_token, "["))
	int_error("cannot set range with replot", c_token);

    /* do not store directly into the replot_line string, until the
     * new plot line has been successfully plotted. This way,
     * if user makes a typo in a replot line, they do not have
     * to start from scratch. The replot_line will be committed
     * after do_plot has returned, whence we know all is well
     */
    if (END_OF_COMMAND) {
	/* it must already be long enough, but lets make sure */
	int len = strlen(replot_line) + 1;
	while (input_line_len < len)
	    extend_input_line();
	strcpy(input_line, replot_line);
    } else {
	char *replot_args = NULL;	/* else m_capture will free it */
	int last_token = num_tokens - 1;
	/* length = length of old part + length of new part + ',' + \0 */
	int newlen = strlen(replot_line) + token[last_token].start_index + token[last_token].length - token[c_token].start_index + 2;
	m_capture(&replot_args, c_token, last_token);	/* might be empty */
	while (input_line_len < newlen)
	    extend_input_line();
	strcpy(input_line, replot_line);
	strcat(input_line, ",");
	strcat(input_line, replot_args);
	free(replot_args);
    }
    plot_token = 0;		/* whole line to be saved as replot line */

    screen_ok = FALSE;
    num_tokens = scanner(&input_line, &input_line_len);
    c_token = 1;		/* skip the 'plot' part */
    if (is_3d_plot)
	plot3drequest();
    else
	plotrequest();
}


/* Support for input, shell, and help for various systems */

#ifdef VMS

# include <descrip.h>
# include <rmsdef.h>
# include <smgdef.h>
# include <smgmsg.h>

extern lib$get_input(), lib$put_output();
extern smg$read_composed_line();
extern sys$putmsg();
extern lbr$output_help();
extern lib$spawn();

int vms_len;

unsigned int status[2] = {1, 0};

static char Help[MAX_LINE_LEN+1] = "gnuplot";

$DESCRIPTOR(prompt_desc, PROMPT);
/* temporary fix until change to variable length */
struct dsc$descriptor_s line_desc =
{0, DSC$K_DTYPE_T, DSC$K_CLASS_S, NULL};

$DESCRIPTOR(help_desc, Help);
$DESCRIPTOR(helpfile_desc, "GNUPLOT$HELP");

/* please note that the vms version of read_line doesn't support variable line
   length (yet) */

static int read_line(prompt)
char *prompt;
{
    int more, start = 0;
    char expand_prompt[40];

    prompt_desc.dsc$w_length = strlen(prompt);
    prompt_desc.dsc$a_pointer = prompt;
    (void) strcpy(expand_prompt, "_");
    (void) strncat(expand_prompt, prompt, 38);
    do {
	line_desc.dsc$w_length = MAX_LINE_LEN - start;
	line_desc.dsc$a_pointer = &input_line[start];
	switch (status[1] = smg$read_composed_line(&vms_vkid, &vms_ktid, &line_desc, &prompt_desc, &vms_len)) {
	case SMG$_EOF:
	    done(IO_SUCCESS);	/* ^Z isn't really an error */
	    break;
	case RMS$_TNS:		/* didn't press return in time */
	    vms_len--;		/* skip the last character */
	    break;		/* and parse anyway */
	case RMS$_BES:		/* Bad Escape Sequence */
	case RMS$_PES:		/* Partial Escape Sequence */
	    sys$putmsg(status);
	    vms_len = 0;	/* ignore the line */
	    break;
	case SS$_NORMAL:
	    break;		/* everything's fine */
	default:
	    done(status[1]);	/* give the error message */
	}
	start += vms_len;
	input_line[start] = NUL;
	inline_num++;
	if (input_line[start - 1] == '\\') {
	    /* Allow for a continuation line. */
	    prompt_desc.dsc$w_length = strlen(expand_prompt);
	    prompt_desc.dsc$a_pointer = expand_prompt;
	    more = 1;
	    --start;
	} else {
	    line_desc.dsc$w_length = strlen(input_line);
	    line_desc.dsc$a_pointer = input_line;
	    more = 0;
	}
    } while (more);
    return 0;
}


# ifdef NO_GIH
static void do_help(toplevel)
int toplevel;			/* not used for VMS version */
{
    int first = c_token;
    while (!END_OF_COMMAND)
	++c_token;

    strcpy(Help, "GNUPLOT ");
    capture(Help + 8, first, c_token - 1, sizeof(Help) - 9);
    help_desc.dsc$w_length = strlen(Help);
    if ((vaxc$errno = lbr$output_help(lib$put_output, 0, &help_desc,
			&helpfile_desc, 0, lib$get_input)) != SS$_NORMAL)
	os_error("can't open GNUPLOT$HELP", NO_CARET);
}

# endif /* NO_GIH */

static void do_shell()
{
    if ((vaxc$errno = lib$spawn()) != SS$_NORMAL) {
	os_error("spawn error", NO_CARET);
    }
}


static void do_system()
{
/*    input_line[0] = ' ';	an embarrassment, but... */

/* input_line is filled by read_line or load_file, but 
 * line_desc length is set only by read_line; adjust now
 */
    line_desc.dsc$w_length = strlen(input_line) - 1;
    line_desc.dsc$a_pointer = &input_line[1];

    if ((vaxc$errno = lib$spawn(&line_desc)) != SS$_NORMAL)
	os_error("spawn error", NO_CARET);

    (void) putc('\n', stderr);
}

#endif /* VMS */


#ifdef _Windows

# ifdef NO_GIH
static void do_help(toplevel)
int toplevel;			/* not used for windows */
{
    if (END_OF_COMMAND)
	WinHelp(textwin.hWndParent, (LPSTR) winhelpname, HELP_INDEX, (DWORD) NULL);
    else {
	char buf[128];
	int start = c_token++;
	while (!(END_OF_COMMAND))
	    c_token++;
	capture(buf, start, c_token - 1, 128);
	WinHelp(textwin.hWndParent, (LPSTR) winhelpname, HELP_PARTIALKEY, (DWORD) buf);
    }
}
# endif /* NO_GIH */
#endif /* _Windows */

/*
 * do_help: (not VMS, although it would work) Give help to the user. It
 * parses the command line into helpbuf and supplies help for that string.
 * Then, if there are subtopics available for that key, it prompts the user
 * with this string. If more input is given, do_help is called recursively,
 * with argument 0.  Thus a more specific help can be supplied. This can be 
 * done repeatedly.  If null input is given, the function returns, effecting 
 * a backward climb up the tree.
 * David Kotz (David.Kotz@Dartmouth.edu) 10/89
 * drd - The help buffer is first cleared when called with toplevel=1. 
 * This is to fix a bug where help is broken if ^C is pressed whilst in the 
 * help.
 */

#ifndef NO_GIH
static void do_help(toplevel)
int toplevel;
{
    static char *helpbuf = NULL;
    static char *prompt = NULL;
    int base;			/* index of first char AFTER help string */
    int len;			/* length of current help string */
    TBOOLEAN more_help;
    TBOOLEAN only;		/* TRUE if only printing subtopics */
    int subtopics;		/* 0 if no subtopics for this topic */
    int start;			/* starting token of help string */
    char *help_ptr;		/* name of help file */
# if defined(SHELFIND)
    static char help_fname[256] = ""; /* keep helpfilename across calls */
# endif

# if defined(MTOS) || defined(ATARI)
    char const *const ext[] = {NULL};
# endif

    if ((help_ptr = getenv("GNUHELP")) == (char *) NULL)
# ifndef SHELFIND
	/* if can't find environment variable then just use HELPFILE */

/* patch by David J. Liu for getting GNUHELP from home directory */
#  if (defined(__TURBOC__) && (defined(MSDOS) || defined(DOS386))) || defined(__DJGPP__)
	help_ptr = HelpFile;
#  else
#   if defined(MTOS) || defined(ATARI)
    {
	if ((help_ptr = findfile(HELPFILE, getenv("GNUPLOTPATH"), ext)) == NULL)
	    help_ptr = findfile(HELPFILE, getenv("PATH"), ext);
	if (!help_ptr)
	    help_ptr = HELPFILE;
    }
#   else
	help_ptr = HELPFILE;
#   endif /* MTOS || ATARI */
#  endif /* __TURBOC__ */
/* end of patch  - DJL */

# else /* !SHELFIND */
	/* try whether we can find the helpfile via shell_find. If not, just
	   use the default. (tnx Andreas) */

	if (!strchr(HELPFILE, ':') && !strchr(HELPFILE, '/') &&
	    !strchr(HELPFILE, '\\')) {
	    if (strlen(help_fname) == 0) {
		strcpy(help_fname, HELPFILE);
		if (shel_find(help_fname) == 0) {
		    strcpy(help_fname, HELPFILE);
		}
	    }
	    help_ptr = help_fname;
	} else {
	    help_ptr = HELPFILE;
	}
# endif /* !SHELFIND */

    /* Since MSDOS DGROUP segment is being overflowed we can not allow such  */
    /* huge static variables (1k each). Instead we dynamically allocate them */
    /* on the first call to this function...                                 */
    if (helpbuf == NULL) {
	helpbuf = gp_alloc(MAX_LINE_LEN, "help buffer");
	prompt = gp_alloc(MAX_LINE_LEN, "help prompt");
	helpbuf[0] = prompt[0] = 0;
    }
    if (toplevel)
	helpbuf[0] = prompt[0] = 0;	/* in case user hit ^c last time */

    len = base = strlen(helpbuf);

    /* find the end of the help command */
    for (start = c_token; !(END_OF_COMMAND); c_token++);
    /* copy new help input into helpbuf */
    if (len > 0)
	helpbuf[len++] = ' ';	/* add a space */
    capture(helpbuf + len, start, c_token - 1, MAX_LINE_LEN - len);
    squash_spaces(helpbuf + base);	/* only bother with new stuff */
    lower_case(helpbuf + base);	/* only bother with new stuff */
    len = strlen(helpbuf);

    /* now, a lone ? will print subtopics only */
    if (strcmp(helpbuf + (base ? base + 1 : 0), "?") == 0) {
	/* subtopics only */
	subtopics = 1;
	only = TRUE;
	helpbuf[base] = NUL;	/* cut off question mark */
    } else {
	/* normal help request */
	subtopics = 0;
	only = FALSE;
    }

    switch (help(helpbuf, help_ptr, &subtopics)) {
    case H_FOUND:{
	    /* already printed the help info */
	    /* subtopics now is true if there were any subtopics */
	    screen_ok = FALSE;

	    do {
		if (subtopics && !only) {
		    /* prompt for subtopic with current help string */
		    if (len > 0)
			(void) sprintf(prompt, "Subtopic of %s: ", helpbuf);
		    else
			(void) strcpy(prompt, "Help topic: ");
		    read_line(prompt);
		    num_tokens = scanner(&input_line, &input_line_len);
		    c_token = 0;
		    more_help = !(END_OF_COMMAND);
		    if (more_help)
			/* base for next level is all of current helpbuf */
			do_help(0);
		} else
		    more_help = FALSE;
	    } while (more_help);

	    break;
	}
    case H_NOTFOUND:{
	    printf("Sorry, no help for '%s'\n", helpbuf);
	    break;
	}
    case H_ERROR:{
	    perror(help_ptr);
	    break;
	}
    default:{			/* defensive programming */
	    int_error("Impossible case in switch", NO_CARET);
	    /* NOTREACHED */
	}
    }

    helpbuf[base] = NUL;	/* cut it off where we started */
}
#endif /* !NO_GIH */


#ifndef VMS

# ifdef AMIGA_AC_5
static char *parms[80];
static char strg0[256];
static void getparms __PROTO((char *, char**));
# endif

static void do_system()
{
# ifdef AMIGA_AC_5
    getparms(input_line + 1, parms);
    fexecv(parms[0], parms);
# elif (defined(ATARI) && defined(__GNUC__))
/* || (defined(MTOS) && defined(__GNUC__)) */
    /* use preloaded shell, if available */
    short (*shell_p) (char *command);
    void *ssp;

    ssp = (void *) Super(NULL);
    shell_p = *(short (**)(char *)) 0x4f6;
    Super(ssp);

    /* this is a bit strange, but we have to have a single if */
    if (shell_p)
	(*shell_p) (input_line + 1);
    else
	system(input_line + 1);
# elif defined(_Windows)
    winsystem(input_line + 1);
# else /* !(AMIGA_AC_5 || ATARI && __GNUC__ || _Windows) */
/* (am, 19980929)
 * OS/2 related note: cmd.exe returns 255 if called w/o argument.
 * i.e. calling a shell by "!" will always end with an error message.
 * A workaround has to include checking for EMX,OS/2, two environment
 *  variables,...
 */
    system(input_line + 1);
# endif /* !(AMIGA_AC_5 || ATARI&&__GNUC__ || _Windows) */
}


# ifdef AMIGA_AC_5
/******************************************************************************
 * Parses the command string (for fexecv use) and  converts the first token
 * to lower case                                                 
 *****************************************************************************/
static void getparms(command, parms)
char *command;
char **parms;
{
    register int i = 0;		/* A bunch of indices */
    register int j = 0;
    register int k = 0;

    while (*(command + j) != NUL) {	/* Loop on string characters */
	parms[k++] = strg0 + i;
	while (*(command + j) == ' ')
	    ++j;
	while (*(command + j) != ' ' && *(command + j) != NUL) {
	    if (*(command + j) == '"')	/* Get quoted string */
		for (*(strg0 + (i++)) = *(command + (j++));
		     *(command + j) != '"';
		     *(strg0 + (i++)) = *(command + (j++)));
	    *(strg0 + (i++)) = *(command + (j++));
	}
	*(strg0 + (i++)) = NUL;	/* NUL terminate every token */
    }
    parms[k] = NUL;

    for (k = strlen(strg0) - 1; k >= 0; --k)	/* Convert to lower case */
	*(strg0 + k) >= 'A' && *(strg0 + k) <= 'Z' ? *(strg0 + k) |= 32 : *(strg0 + k);
}

# endif /* AMIGA_AC_5 */


# if defined(READLINE) || defined(HAVE_LIBREADLINE)
/* keep some compilers happy */
static char *rlgets __PROTO((char *s, int n, char *prompt));

static char * rlgets(s, n, prompt)
char *s;
int n;
char *prompt;
{
    static char *line = (char *) NULL;
    static int leftover = -1;	/* index of 1st char leftover from last call */

    if (leftover == -1) {
	/* If we already have a line, first free it */
	if (line != (char *) NULL) {
	    free(line);
	    line = NULL;
	    /* so that ^C or int_error during readline() does
	     * not result in line being free-ed twice */
	}
	line = readline((interactive) ? prompt : "");
	leftover = 0;
	/* If it's not an EOF */
	if (line && *line)
	    add_history(line);
    }
    if (line) {
	safe_strncpy(s, line + leftover, n);
	leftover += strlen(s);
	if (line[leftover] == NUL)
	    leftover = -1;
	return s;
    }
    return NULL;
}
# endif /* READLINE || HAVE_LIBREADLINE */


# if defined(MSDOS) || defined(_Windows) || defined(DOS386)
static void do_shell()
{
    register char *comspec;
    if ((comspec = getenv("COMSPEC")) == (char *) NULL)
	comspec = "\\command.com";
#  ifdef _Windows
    if (WinExec(comspec, SW_SHOWNORMAL) <= 32)
#  else
#   ifdef DJGPP
    if (system(comspec) == -1)
#   else
    if (spawnl(P_WAIT, comspec, NULL) == -1)
#   endif /* !DJGPP */
#  endif /* !_Windows */
	os_error("unable to spawn shell", NO_CARET);
}

# else /* !MSDOS */

/* plain old Unix */

#  ifdef AMIGA_SC_6_1
static void do_shell()
{
    register char *shell;
    if (!(shell = getenv("SHELL")))
	shell = SHELL;

    if (system(shell))
	os_error("system() failed", NO_CARET);

    (void) putc('\n', stderr);
}

#  else /* !AMIGA_SC_6_1 */

#   ifdef OS2
static void do_shell()
{
    register char *shell;
    if (!(shell = getenv("SHELL")) && !(shell = getenv("COMSPEC")))
	shell = SHELL;

    if (system(shell) == -1)
	os_error("system() failed", NO_CARET);

    (void) putc('\n', stderr);
}
#   else /* !OS2 */

#define EXEC "exec "
static void do_shell()
{
    static char exec[100] = EXEC;
    register char *shell;
    if (!(shell = getenv("SHELL")))
	shell = SHELL;

    if (system(safe_strncpy(&exec[sizeof(EXEC) - 1], shell,
		       sizeof(exec) - sizeof(EXEC) - 1)))
	os_error("system() failed", NO_CARET);

    (void) putc('\n', stderr);
}

#   endif /* !OS2 */
#  endif /* !AMIGA_SC_6_1 */
# endif /* !MSDOS */

/* read from stdin, everything except VMS */

# if !defined(READLINE) && !defined(HAVE_LIBREADLINE)
#  if (defined(MSDOS) || defined(DOS386)) && !defined(_Windows) && !defined(__EMX__) && !defined(DJGPP)

/* if interactive use console IO so CED will work */

#define PUT_STRING(s) cputs(s)
#define GET_STRING(s,l) ((interactive) ? cgets_emu(s,l) : fgets(s,l,stdin))

#   ifdef __TURBOC__
/* cgets implemented using dos functions */
/* Maurice Castro 22/5/91 */
static char *doscgets __PROTO((char *));

static char *doscgets(s)
char *s;
{
    long datseg;

    /* protect and preserve segments - call dos to do the dirty work */
    datseg = _DS;

    _DX = FP_OFF(s);
    _DS = FP_SEG(s);
    _AH = 0x0A;
    geninterrupt(33);
    _DS = datseg;

    /* check for a carriage return and then clobber it with a null */
    if (s[s[1] + 2] == '\r')
	s[s[1] + 2] = 0;

    /* return the input string */
    return (&(s[2]));
}
#   endif /* __TURBOC__ */

#   ifdef __ZTC__
void cputs(char *s)
{
    register int i = 0;
    while (s[i] != NUL)
	bdos(0x02, s[i++], NULL);
}

char *cgets(char *s)
{
    bdosx(0x0A, s, NULL);

    if (s[s[1] + 2] == '\r')
	s[s[1] + 2] = 0;

    /* return the input string */
    return (&(s[2]));
}
#   endif /* __ZTC__ */

/* emulate a fgets like input function with DOS cgets */
char *cgets_emu(str, len)
char *str;
int len;
{
    static char buffer[128] = "";
    static int leftover = 0;

    if (buffer[leftover] == NUL) {
	buffer[0] = 126;
#   ifdef __TURBOC__
	doscgets(buffer);
#   else
	cgets(buffer);
#   endif
	fputc('\n', stderr);
	if (buffer[2] == 26)
	    return NULL;
	leftover = 2;
    }
    safe_strncpy(str, buffer + leftover, len);
    leftover += strlen(str);
    return str;
}
#  else /* !plain DOS */

#   define PUT_STRING(s) fputs(s, stderr)
#   define GET_STRING(s,l) fgets(s, l, stdin)

#  endif /* !plain DOS */
# endif /* !READLINE && !HAVE_LIBREADLINE) */

/* Non-VMS version */
static int read_line(prompt)
char *prompt;
{
    int start = 0;
    TBOOLEAN more = FALSE;
    int last = 0;

# if !defined(READLINE) && !defined(HAVE_LIBREADLINE)
    if (interactive)
	PUT_STRING(prompt);
# endif /* READLINE */
    do {
	/* grab some input */
# if defined(READLINE) || defined(HAVE_LIBREADLINE)
	if (((interactive)
	     ? rlgets(&(input_line[start]), input_line_len - start,
		      ((more) ? "> " : prompt))
	     : fgets(&(input_line[start]), input_line_len - start, stdin))
	    == (char *) NULL) {
# else /* !(READLINE || HAVE_LIBREADLINE) */
	if (GET_STRING(&(input_line[start]), input_line_len - start)
	    == (char *) NULL) {
# endif /* !(READLINE || HAVE_LIBREADLINE) */
	    /* end-of-file */
	    if (interactive)
		(void) putc('\n', stderr);
	    input_line[start] = NUL;
	    inline_num++;
	    if (start > 0)	/* don't quit yet - process what we have */
		more = FALSE;
	    else
		return (1);	/* exit gnuplot */
	} else {
	    /* normal line input */
	    last = strlen(input_line) - 1;
	    if (last >= 0) {
		if (input_line[last] == '\n') {		/* remove any newline */
		    input_line[last] = NUL;
		    /* Watch out that we don't backup beyond 0 (1-1-1) */
		    if (last > 0)
			--last;
		} else if (last + 2 >= input_line_len) {
		    extend_input_line();
		    start = last + 1;
		    more = TRUE;
		    continue;	/* read rest of line, don't print "> " */
		}
		if (input_line[last] == '\\') {		/* line continuation */
		    start = last;
		    more = TRUE;
		} else
		    more = FALSE;
	    } else
		more = FALSE;
	}
# if !defined(READLINE) && !defined(HAVE_LIBREADLINE)
	if (more && interactive)
	    PUT_STRING("> ");
# endif
    } while (more);
    return (0);
}
#endif /* !VMS */

#ifdef _Windows
/* there is a system like call on MS Windows but it is a bit difficult to 
   use, so we will invoke the command interpreter and use it to execute the 
   commands */
static int winsystem(char *s)
{
    LPSTR comspec;
    LPSTR execstr;
    LPSTR p;

    /* get COMSPEC environment variable */
# ifdef WIN32
    char envbuf[81];
    GetEnvironmentVariable("COMSPEC", envbuf, 80);
    if (*envbuf == NUL)
	comspec = "\\command.com";
    else
	comspec = envbuf;
# else
    p = GetDOSEnvironment();
    comspec = "\\command.com";
    while (*p) {
	if (!strncmp(p, "COMSPEC=", 8)) {
	    comspec = p + 8;
	    break;
	}
	p += strlen(p) + 1;
    }
# endif
    /* if the command is blank we must use command.com */
    p = s;
    while ((*p == ' ') || (*p == '\n') || (*p == '\r'))
	p++;
    if (*p == NUL) {
	WinExec(comspec, SW_SHOWNORMAL);
    } else {
	/* attempt to run the windows/dos program via windows */
	if (WinExec(s, SW_SHOWNORMAL) <= 32) {
	    /* attempt to run it as a dos program from command line */
	    execstr = (char *) malloc(strlen(s) + strlen(comspec) + 6);
	    strcpy(execstr, comspec);
	    strcat(execstr, " /c ");
	    strcat(execstr, s);
	    WinExec(execstr, SW_SHOWNORMAL);
	    free(execstr);
	}
    }

    /* regardless of the reality return OK - the consequences of */
    /* failure include shutting down Windows */
    return (0);			/* success */
}
#endif /* _Windows */

