See More

/* GNUPLOT - command.c */ /*[ * Copyright 1986 - 1993, 1998, 2004 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: * * 19 September 1992 Lawrence Crowl ([email protected]) * Added user-specified bases for log scaling. * * April 1999 Franz Bakan ([email protected]) * Added code to support mouse-input from OS/2 PM window * Changes marked by USE_MOUSE * * May 1999, update by Petr Mikulik * Use gnuplot's pid in shared mem name * * August 1999 Franz Bakan and Petr Mikulik * Encapsulating read_line into a thread, acting on input when thread or * gnupmdrv posts an event semaphore. Thus mousing works even when gnuplot * is used as a plotting device (commands passed via pipe). * * May 2011 Ethan A Merritt * Introduce block structure defined by { ... }, which may span multiple lines. * In order to have the entire block available at one time we now count * +/- curly brackets during input and keep extending the current input line * until the net count is zero. This is done in do_line() for interactive * input, and load_file() for non-interactive input. */ #include "command.h" #include "axis.h" #include "alloc.h" #include "datablock.h" #include "eval.h" #include "fit.h" #include "datafile.h" #include "getcolor.h" #include "gp_hist.h" #include "gplocale.h" #include "loadpath.h" #include "misc.h" #include "parse.h" #include "plot.h" #include "plot2d.h" #include "plot3d.h" #include "readline.h" #include "save.h" #include "scanner.h" #include "setshow.h" #include "stats.h" #include "tables.h" #include "term_api.h" #include "util.h" #include "voxelgrid.h" #include "external.h" #ifdef USE_MOUSE # include "mouse.h" int paused_for_mouse = 0; #endif #define PROMPT "gnuplot> " #ifdef OS2_IPC # define INCL_DOSMEMMGR # define INCL_DOSPROCESS # define INCL_DOSSEMAPHORES # include static char *input_line_SharedMem = NULL; /* pointer to the shared memory for mouse messages */ static HEV semInputReady = 0; /* mouse event semaphore */ static HEV semPause = 0; /* pause event semaphore */ static TBOOLEAN thread_rl_Running = FALSE; /* running status */ static int thread_rl_RetCode = -1; /* return code from readline input thread */ static int thread_pause_RetCode = -1; /* return code from pause input thread */ static TBOOLEAN pause_internal; /* flag to indicate not to use a dialog box */ #endif /* OS2_IPC */ #ifndef _WIN32 # include "help.h" #endif #ifdef _WIN32 # define WIN32_LEAN_AND_MEAN # include # ifdef _MSC_VER # include # include /* getcwd() */ # else # include # endif # include # include "win/winmain.h" #endif /* _WIN32 */ #ifdef __DJGPP__ # include /* getkey() */ # define useconds_t unsigned #endif #ifdef __WATCOMC__ # include /* for getch() */ #endif typedef enum ifstate {IF_INITIAL=1, IF_TRUE, IF_FALSE} ifstate; /* static prototypes */ static void command(void); static TBOOLEAN is_array_assignment(void); static int changedir(char *path); static char* fgets_ipc(char* dest, int len); static char* gp_get_string(char *, size_t, const char *); static int read_line(const char *prompt, int start); static void do_system(const char *); static void test_palette_subcommand(void); static int find_clause(int *, int *); static void if_else_command(ifstate if_state); static void old_if_command(struct at_type *expr); static int report_error(int ierr); static void load_or_call_command( TBOOLEAN call ); void do_shell(void); static int expand_1level_macros(void); struct lexical_unit *token; int token_table_size; char *gp_input_line; size_t gp_input_line_len; int inline_num; /* input line number */ /* Points to structure holding dummy parameter values * to be used during function evaluation */ struct udft_entry *dummy_func; /* support for replot command */ char *replot_line = NULL; int plot_token = 0; /* start of 'plot' command */ /* flag to disable `replot` when some data are sent through stdin; * used by mouse/hotkey capable terminals */ TBOOLEAN replot_disabled = FALSE; /* flag to show we are inside a plot/splot/replot/refresh/stats * command and therefore should not allow starting another one * e.g. from a function block */ TBOOLEAN inside_plot_command = FALSE; /* output file for the print command */ FILE *print_out = NULL; struct udvt_entry *print_out_var = NULL; char *print_out_name = NULL; char *print_sep = NULL; /* input data, parsing variables */ int num_tokens, c_token; TBOOLEAN if_open_for_else = FALSE; static int clause_depth = 0; /* support for 'break' and 'continue' commands */ static int iteration_depth = 0; static TBOOLEAN requested_break = FALSE; static TBOOLEAN requested_continue = FALSE; /* set when an "exit" command is encountered */ static int command_exit_requested = 0; /* support for dynamic size of input line */ void extend_input_line() { if (gp_input_line_len == 0) { /* first time */ gp_input_line = gp_alloc(MAX_LINE_LEN, "gp_input_line"); gp_input_line_len = MAX_LINE_LEN; gp_input_line[0] = NUL; } else { gp_input_line = gp_realloc(gp_input_line, gp_input_line_len + MAX_LINE_LEN, "extend input line"); gp_input_line_len += MAX_LINE_LEN; FPRINTF((stderr, "extending input line to %d chars\n", gp_input_line_len)); } } /* constant by which token table grows */ #define MAX_TOKENS 400 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; /* HBB: for checker-runs: */ memset(token, 0, MAX_TOKENS * sizeof(*token)); } else { token = gp_realloc(token, (token_table_size + MAX_TOKENS) * sizeof(struct lexical_unit), "extend token table"); memset(token+token_table_size, 0, MAX_TOKENS * sizeof(*token)); token_table_size += MAX_TOKENS; FPRINTF((stderr, "extending token table to %d elements\n", token_table_size)); } } #ifdef OS2_IPC void thread_read_line(void *arg) { (void) arg; thread_rl_Running = TRUE; thread_rl_RetCode = read_line(PROMPT, 0); thread_rl_Running = FALSE; DosPostEventSem(semInputReady); } void thread_pause(void *arg) { int rc = 2; if (!pause_internal) rc = PM_pause(arg != NULL ? arg : "paused"); if (rc == 2) { /* rc==2: no dialog, listen to stdin */ int junk; if (arg != NULL) fputs(arg, stderr); do { junk = fgetc(stdin); } while (junk != EOF && junk != '\r' && junk != '\n'); thread_pause_RetCode = (junk == EOF) ? 0 : 1; } if (rc == 3) { /* dialog/menu active, wait for event semaphore */ ULONG u; DosResetEventSem(semPause, &u); DosWaitEventSem(semPause, SEM_INDEFINITE_WAIT); /* now query result */ thread_pause_RetCode = PM_pause(NULL); } else { /* rc==1: OK; rc==0: cancel */ thread_pause_RetCode = rc; } DosPostEventSem(semInputReady); } void os2_ipc_setup(void) { APIRET rc; char name[40]; /* create input event semaphore */ sprintf(name, "\\SEM32\\GP%i_Input_Ready", getpid()); rc = DosCreateEventSem(name, &semInputReady, 0, 0); if (rc != 0) fputs("DosCreateEventSem error\n", stderr); /* create pause event semaphore */ sprintf(name, "\\SEM32\\GP%i_Pause_Ready", getpid()); rc = DosCreateEventSem(name, &semPause, 0, 0); if (rc != 0) fputs("DosCreateEventSem error\n", stderr); /* allocate shared memory */ sprintf(name, "\\SHAREMEM\\GP%i_Mouse_Input", getpid()); rc = DosAllocSharedMem((PPVOID) &input_line_SharedMem, name, MAX_LINE_LEN, PAG_READ | PAG_WRITE | PAG_COMMIT); if (rc != 0) fputs("DosAllocSharedMem ERROR\n", stderr); else *input_line_SharedMem = 0; } int os2_ipc_dispatch_event(void) { if (input_line_SharedMem == NULL || !*input_line_SharedMem) return 0; if (*input_line_SharedMem == '%') { struct gp_event_t ge; /* copy event data immediately */ memcpy(&ge, input_line_SharedMem + 1, sizeof(ge)); *input_line_SharedMem = 0; /* discard the event data */ thread_rl_RetCode = 0; /* process event */ do_event(&ge); /* end pause mouse? */ if ((ge.type == GE_buttonrelease) && (paused_for_mouse & PAUSE_CLICK) && (((ge.par1 == 1) && (paused_for_mouse & PAUSE_BUTTON1)) || ((ge.par1 == 2) && (paused_for_mouse & PAUSE_BUTTON2)) || ((ge.par1 == 3) && (paused_for_mouse & PAUSE_BUTTON3)))) { paused_for_mouse = 0; } if ((ge.type == GE_keypress) && (paused_for_mouse & PAUSE_KEYSTROKE) && (ge.par1 != NUL)) { paused_for_mouse = 0; } return 0; } if (*input_line_SharedMem && strstr(input_line_SharedMem, "plot") != NULL && (strcmp(term->name, "pm") != 0 && strcmp(term->name, "x11") != 0)) { /* avoid plotting if terminal is not PM or X11 */ fprintf(stderr, "\n\tCommand(s) ignored for other than PM and X11 terminals\a\n"); if (interactive) fputs(PROMPT, stderr); *input_line_SharedMem = 0; /* discard the event data */ return 0; } strcpy(gp_input_line, input_line_SharedMem); input_line_SharedMem[0] = 0; thread_rl_RetCode = 0; return 1; } int os2_ipc_waitforinput(int mode) { ULONG u; if (mode == TERM_ONLY_CHECK_MOUSING) { if (semInputReady == 0) return 0; if (DosWaitEventSem(semInputReady, SEM_IMMEDIATE_RETURN) == 0) { os2_ipc_dispatch_event(); DosResetEventSem(semInputReady, &u); } } return 0; } #endif /* OS2_IPC */ int com_line() { if (multiplot) { /* calls int_error() if it is not happy */ term_check_multiplot_okay(interactive); if (read_line("multiplot> ", 0)) return (1); } else { #if defined(OS2_IPC) && defined(USE_MOUSE) ULONG u; if (!thread_rl_Running) { int res = _beginthread(thread_read_line, NULL, 32768, NULL); if (res == -1) fputs("error command.c could not begin thread\n", stderr); } /* wait until a line is read or gnupmdrv makes shared mem available */ DosWaitEventSem(semInputReady, SEM_INDEFINITE_WAIT); DosResetEventSem(semInputReady, &u); if (thread_rl_Running) { /* input thread still running, this must be a "mouse" event */ if (os2_ipc_dispatch_event() == 0) return 0; } if (thread_rl_RetCode) return 1; #else /* The normal case */ if (read_line(PROMPT, 0)) return 1; #endif /* defined(OS2_IPC) && defined(USE_MOUSE) */ } /* 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(). * Expand any string variables in the current input line. */ string_expand_macros(); /* Remove leading whitespace */ { char *inlptr = gp_input_line; while (isspace((unsigned char) *inlptr)) inlptr++; if (inlptr != gp_input_line) { memmove(gp_input_line, inlptr, strlen(inlptr)); gp_input_line[strlen(inlptr)] = NUL; } } /* Leading '!' indicates a shell command that bypasses normal gnuplot * tokenization and parsing. * This doesn't work inside a bracketed clause or in a function block. */ if (is_system(*gp_input_line)) { if (evaluate_inside_functionblock) int_error(NO_CARET, "bare shell commands not accepted in a function block"); do_system(gp_input_line + 1); return (0); } /* Strip off trailing comment */ if (strchr(gp_input_line, '#')) { num_tokens = scanner(&gp_input_line, &gp_input_line_len); if (gp_input_line[token[num_tokens].start_index] == '#') gp_input_line[token[num_tokens].start_index] = NUL; } num_tokens = scanner(&gp_input_line, &gp_input_line_len); /* * Expand line if necessary to contain a complete bracketed clause {...} * Insert a ';' after current line and append the next input line. * NB: This may leave an "else" condition on the next line. */ if (curly_brace_count < 0) int_error(NO_CARET,"Unexpected }"); while (curly_brace_count > 0) { if (lf_head && lf_head->depth > 0) { /* This catches the case that we are inside a "load foo" operation * and therefore requesting interactive input is not an option. */ int_error(NO_CARET, "Syntax error: missing block terminator }"); } else if (interactive || noinputfiles) { /* If we are really in interactive mode and there are unterminated blocks, * then we want to display a "more>" prompt to get the rest of the block. * However, there are two more cases that must be dealt here: * One is when commands are piped to gnuplot - on the command line, * the other is when commands are piped to gnuplot which is opened * as a slave process. The test for noinputfiles is for the latter case. * If we didn't have that test here, unterminated blocks sent via a pipe * would trigger the error message in the else branch below. */ int retval; strcat(gp_input_line,";"); retval = read_line("more> ", strlen(gp_input_line)); if (retval) int_error(NO_CARET, "Syntax error: missing block terminator }"); /* Expand any string variables in the current input line */ string_expand_macros(); num_tokens = scanner(&gp_input_line, &gp_input_line_len); if (gp_input_line[token[num_tokens].start_index] == '#') gp_input_line[token[num_tokens].start_index] = NUL; } else { /* Non-interactive mode here means that we got a string from -e. * Having curly_brace_count > 0 means that there are at least one * unterminated blocks in the string. * Likely user error, so we die with an error message. */ int_error(NO_CARET, "Syntax error: missing block terminator }"); } } c_token = 0; while (c_token < num_tokens) { command(); if (command_exit_requested) { command_exit_requested = 0; /* yes this is necessary */ return 1; } if (iteration_early_exit()) { c_token = num_tokens; break; } if (c_token < num_tokens) { /* something after command */ if (equals(c_token, ";")) { c_token++; } else if (equals(c_token, "{")) { begin_clause(); } else if (equals(c_token, "}")) { end_clause(); } else int_error(c_token, "unexpected or unrecognized token: %s", token_to_string(c_token)); } } /* This check allows event handling inside load/eval/while statements */ check_for_mouse_events(); return (0); } void do_string(const char *s) { char *cmdline = gp_strdup(s); do_string_and_free(cmdline); } void do_string_and_free(char *cmdline) { #ifdef USE_MOUSE if (display_ipc_commands()) fprintf(stderr, "%s\n", cmdline); #endif lf_push(NULL, NULL, cmdline); /* save state for errors and recursion */ while (gp_input_line_len < strlen(cmdline) + 1) extend_input_line(); strcpy(gp_input_line, cmdline); screen_ok = FALSE; command_exit_requested = do_line(); /* "exit" is supposed to take us out of the current file from a * "load " command. But the LFS stack holds both files and * bracketed clauses, so we have to keep popping until we hit an * actual file. */ if (command_exit_requested) { while (lf_head && !lf_head->name) { FPRINTF((stderr,"pop one level of non-file LFS\n")); lf_pop(); } } else lf_pop(); } #ifdef USE_MOUSE void toggle_display_of_ipc_commands() { if (mouse_setting.verbose) mouse_setting.verbose = 0; else mouse_setting.verbose = 1; } int display_ipc_commands() { return mouse_setting.verbose; } void do_string_replot(const char *s) { do_string(s); if (volatile_data && (E_REFRESH_NOT_OK != refresh_ok)) { if (display_ipc_commands()) fprintf(stderr, "refresh\n"); refresh_request(); } else if (!replot_disabled) replotrequest(); else int_warn(NO_CARET, "refresh not possible and replot is disabled"); } void restore_prompt() { if (interactive) { #if defined(HAVE_LIBREADLINE) || defined(HAVE_LIBEDITLINE) # if !defined(MISSING_RL_FORCED_UPDATE_DISPLAY) rl_forced_update_display(); # else rl_redisplay(); # endif #else fputs(PROMPT, stderr); fflush(stderr); #endif } } #endif /* USE_MOUSE */ void define() { int start_token; /* the 1st token in the function definition */ struct udvt_entry *udv; struct udft_entry *udf; struct value result; if (equals(c_token + 1, "(")) { /* function ! */ int dummy_num = 0; struct at_type *at_tmp; char *tmpnam; 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(c_token + 2, "function contains too many parameters"); c_token += 3; /* skip (, dummy, ) and = */ if (END_OF_COMMAND) int_error(c_token, "function definition expected"); udf = dummy_func = add_udf(start_token); udf->dummy_num = dummy_num; if ((at_tmp = perm_at()) == (struct at_type *) NULL) int_error(start_token, "not enough memory for function"); if (udf->at) /* already a dynamic a.t. there */ free_at(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 */ /* Save function definition in a user-accessible variable */ tmpnam = gp_alloc(8+strlen(udf->udf_name), "varname"); strcpy(tmpnam, "GPFUN_"); strcat(tmpnam, udf->udf_name); fill_gpval_string(tmpnam, udf->definition); free(tmpnam); } else { /* variable ! */ char *varname = gp_input_line + token[c_token].start_index; if (!strncmp(varname, "GPVAL_", 6) || !strncmp(varname, "GPFUN_", 6) || !strncmp(varname, "MOUSE_", 6)) int_error(c_token, "Cannot set internal variables GPVAL_ GPFUN_ MOUSE_"); start_token = c_token; c_token += 2; const_express(&result); /* Special handling needed to safely return an array */ if (result.type == ARRAY) make_array_permanent(&result); /* If the variable name was previously in use then depending on its * old type it may have attached memory that needs to be freed. * Note: supposedly the old variable type cannot be datablock because * the syntax $name = foo is not accepted so we would not be here. * However, weird cases like FOO = value($datablock) violate this rule * and leave FOO as a datablock whose name does not start with $. */ udv = add_udv(start_token); free_value(&udv->udv_value); udv->udv_value = result; } } void undefine_command() { char key[MAX_ID_LEN+1]; TBOOLEAN wildcard; c_token++; /* consume the command name */ while (!END_OF_COMMAND) { /* copy next var name into key */ copy_str(key, c_token, MAX_ID_LEN); /* Peek ahead - must do this, because a '*' is returned as a separate token, not as part of the 'key' */ wildcard = equals(c_token+1,"*"); if (wildcard) c_token++; /* The '$' starting a data block name is a separate token */ else if (*key == '$') copy_str(&key[1], ++c_token, MAX_ID_LEN-1); /* Other strange stuff on command line */ else if (!isletter(c_token)) int_error(c_token, "Not a variable name"); /* This command cannot deal with array elements or functions */ if (equals(c_token+1, "[") || equals(c_token+1, "(")) int_error(c_token, "Cannot undefine function or array element"); /* ignore internal variables */ if (strncmp(key, "GPVAL_", 6) && strncmp(key, "MOUSE_", 6)) del_udv_by_name( key, wildcard ); c_token++; } } static void command() { int i; 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 (is_array_assignment()) ; else (*lookup_ftable(&command_ftbl[0],c_token))(); return; } /* process the 'raise' or 'lower' command */ void raise_lower_command(int lower) { ++c_token; if (END_OF_COMMAND) { if (lower) { #ifdef OS2 pm_lower_terminal_window(); #endif #ifdef X11 x11_lower_terminal_group(); #endif #ifdef _WIN32 win_lower_terminal_group(); #endif #ifdef WXWIDGETS wxt_lower_terminal_group(); #endif } else { #ifdef OS2 pm_raise_terminal_window(); #endif #ifdef X11 x11_raise_terminal_group(); #endif #ifdef _WIN32 win_raise_terminal_group(); #endif #ifdef WXWIDGETS wxt_raise_terminal_group(); #endif } return; } else { int number; int negative = equals(c_token, "-"); if (negative || equals(c_token, "+")) c_token++; if (!END_OF_COMMAND && isanumber(c_token)) { number = real_expression(); if (negative) number = -number; if (lower) { #ifdef OS2 pm_lower_terminal_window(); #endif #ifdef X11 x11_lower_terminal_window(number); #endif #ifdef _WIN32 win_lower_terminal_window(number); #endif #ifdef WXWIDGETS wxt_lower_terminal_window(number); #endif } else { #ifdef OS2 pm_raise_terminal_window(); #endif #ifdef X11 x11_raise_terminal_window(number); #endif #ifdef _WIN32 win_raise_terminal_window(number); #endif #ifdef WXWIDGETS wxt_raise_terminal_window(number); #endif } ++c_token; return; } } if (lower) int_error(c_token, "usage: lower {plot_id}"); else int_error(c_token, "usage: raise {plot_id}"); } void raise_command(void) { raise_lower_command(0); } void lower_command(void) { raise_lower_command(1); } /* * Arrays are declared using the syntax * array A[size] { = [ element, element, ... ] } * array A = [ .., .. ] * array A = (only valid if returns an array) * where size is an integer and space is reserved for elements A[1] through A[size] * The size itself is stored in A[0].v.int_val.A * The list of initial values is optional. * Any element that is not initialized is set to NOTDEFINED. * * Elements in an existing array can be accessed like any other gnuplot variable. * Each element can be one of INTGR, CMPLX, STRING. */ void array_command() { int nsize = 0; /* Size of array when we leave */ int est_size = 0; /* Estimated size */ TBOOLEAN empty_array = FALSE; struct udvt_entry *array; struct value *A; int i; /* Create or recycle a udv containing an array with the requested name */ if (!isletter(++c_token)) int_error(c_token, "illegal variable name"); array = add_udv(c_token++); if (equals(c_token, "[")) { c_token++; nsize = int_expression(); if (!equals(c_token++,"]")) int_error(c_token-1, "expecting array[size>0]"); } else if (equals(c_token, "=") && equals(c_token+1, "[")) { if (equals(c_token+2,"]")) empty_array = TRUE; /* Estimate size of array by counting commas in the initializer */ for ( i = c_token+2; i < num_tokens; i++) { if (equals(i,",") || equals(i,"]")) est_size++; if (equals(i,"]")) break; } nsize = est_size; } else if (equals(c_token, "=")) { /* array A = */ struct value a; int save_token = ++c_token; const_express(&a); if (a.type != ARRAY) { free_value(&a); int_error(save_token, "not an array expression"); } make_array_permanent(&a); array->udv_value = a; return; } if (nsize > 0) init_array(array, nsize); else int_error(c_token-1, "expecting array[size>0]"); /* Element zero can also hold an indicator that this is a colormap */ A = array->udv_value.v.value_array; if (equals(c_token, "colormap")) { c_token++; if (nsize >= 2) /* Need at least 2 entries to calculate range */ A[0].type = COLORMAP_ARRAY; } /* Initializer syntax: array A[10] = [x,y,z,,"foo",] */ if (equals(c_token, "=") && equals(c_token+1, "[")) { int initializers = 0; c_token += 2; for (i = 1; i <= nsize; i++) { if (equals(c_token, "]")) break; if (equals(c_token, ",")) { initializers++; c_token++; continue; } const_express(&A[i]); if (A[i].type == ARRAY) { if (A[i].v.value_array[0].type == TEMP_ARRAY) gpfree_array(&(A[i])); A[i].type = NOTDEFINED; int_error(c_token, "Cannot nest arrays"); } initializers++; if (equals(c_token, "]")) break; if (equals(c_token, ",")) c_token++; else int_error(c_token, "expecting Array[size] = [x,y,...]"); } c_token++; /* If the size is determined by the number of initializers */ if (empty_array) A[0].v.int_val = 0; else if (A[0].v.int_val == 0) A[0].v.int_val = initializers; } return; } /* * Check for command line beginning with * Array[] = * This routine is modeled on command.c:define() */ TBOOLEAN is_array_assignment() { udvt_entry *udv; struct value newvalue; int index; TBOOLEAN looks_OK = FALSE; int brackets; if (!isletter(c_token) || !equals(c_token+1, "[")) return FALSE; /* There are other legal commands where the 2nd token is [ * e.g. "plot [min:max] foo" * so we check that the closing ] is immediately followed by =. */ for (index=c_token+2, brackets=1; index < num_tokens; index++) { if (equals(index,";")) return FALSE; if (equals(index,"[")) brackets++; if (equals(index,"]")) brackets--; if (brackets == 0) { if (!equals(index+1,"=")) return FALSE; looks_OK = TRUE; break; } } if (!looks_OK) return FALSE; udv = add_udv(c_token); if (udv->udv_value.type != ARRAY) int_error(c_token, "Not a known array"); /* Evaluate index */ c_token += 2; index = int_expression(); if (index <= 0 || index > udv->udv_value.v.value_array[0].v.int_val) int_error(c_token, "array index out of range"); if (!equals(c_token, "]") || !equals(c_token+1, "=")) int_error(c_token, "Expecting Arrayname[] = "); /* Evaluate right side of assignment */ c_token += 2; const_express(&newvalue); if (newvalue.type == ARRAY) { if (newvalue.v.value_array[0].type == TEMP_ARRAY) gpfree_array(&newvalue); newvalue.type = NOTDEFINED; int_error(c_token, "Cannot nest arrays"); } gpfree_string(&udv->udv_value.v.value_array[index]); udv->udv_value.v.value_array[index] = newvalue; return TRUE; } #ifdef USE_MOUSE /* process the 'bind' command */ /* EAM - rewritten 2015 */ void bind_command() { char* lhs = NULL; char* rhs = NULL; TBOOLEAN allwindows = FALSE; ++c_token; if (almost_equals(c_token,"all$windows")) { allwindows = TRUE; c_token++; } /* get left hand side: the key or key sequence * either (1) entire sequence is in quotes * or (2) sequence goes until the first whitespace */ if (END_OF_COMMAND) { ; /* Fall through */ } else if ((lhs = try_to_get_string())) { FPRINTF((stderr,"Got bind quoted lhs = \"%s\"\n",lhs)); } else { char *first = gp_input_line + token[c_token].start_index; int size = strcspn(first, " \";"); lhs = gp_alloc(size + 1, "bind_command->lhs"); strncpy(lhs, first, size); lhs[size] = '\0'; FPRINTF((stderr,"Got bind unquoted lhs = \"%s\"\n",lhs)); while (gp_input_line + token[c_token].start_index < first+size) c_token++; } /* get right hand side: the command to bind * either (1) quoted command * or (2) the rest of the line */ if (END_OF_COMMAND) { ; /* Fall through */ } else if ((rhs = try_to_get_string())) { FPRINTF((stderr,"Got bind quoted rhs = \"%s\"\n",rhs)); } else { int save_token = c_token; while (!END_OF_COMMAND) c_token++; m_capture( &rhs, save_token, c_token-1 ); FPRINTF((stderr,"Got bind unquoted rhs = \"%s\"\n",rhs)); } /* bind_process() will eventually free lhs / rhs ! */ bind_process(lhs, rhs, allwindows); } #endif /* USE_MOUSE */ /* * 'break' and 'continue' commands act as in the C language. * Skip to the end of current loop iteration and (for break) * do not iterate further */ void break_command() { c_token++; if (iteration_depth == 0) return; /* Skip to end of current iteration */ c_token = num_tokens; /* request that subsequent iterations should be skipped also */ requested_break = TRUE; } void continue_command() { c_token++; if (iteration_depth == 0) return; /* Skip to end of current clause */ c_token = num_tokens; /* request that remainder of this iteration be skipped also */ requested_continue = TRUE; } TBOOLEAN iteration_early_exit() { return (requested_continue || requested_break); } /* * Command parser functions */ /* process the 'cd' command */ void changedir_command() { char *save_file = NULL; c_token++; save_file = try_to_get_string(); if (!save_file) int_error(c_token, "expecting directory name"); gp_expand_tilde(&save_file); if (changedir(save_file)) int_error(c_token, "Can't change to this directory"); else update_gpval_variables(5); free(save_file); } /* process the 'clear' command */ void clear_command() { term_start_plot(); if (multiplot && term->fillbox) { int xx1 = xoffset * term->xmax; int yy1 = yoffset * term->ymax; unsigned int width = xsize * term->xmax; unsigned int height = ysize * term->ymax; (*term->fillbox) (0, xx1, yy1, width, height); } term_end_plot(); screen_ok = FALSE; c_token++; } /* process the 'evaluate' command */ void eval_command() { char *command; c_token++; #ifdef USE_FUNCTIONBLOCKS if (equals(c_token, "$") && isletter(c_token+1) && equals(c_token+2,"(")) { (void)int_expression(); /* throw away any actual return value */ return; } #endif command = try_to_get_string(); if (!command) int_error(c_token, "Expected command string"); do_string_and_free(command); } /* process the 'exit' and 'quit' commands */ void exit_command() { /* If the command is "exit gnuplot" then do so */ if (equals(c_token+1,"gnuplot")) gp_exit(EXIT_SUCCESS); if (equals(c_token+1,"status")) { int status; c_token += 2; status = int_expression(); gp_exit(status); } /* exit error 'error message' returns to the top command line */ if (equals(c_token+1,"error")) { c_token += 2; int_error(NO_CARET, try_to_get_string()); } /* else graphics will be tidied up in main */ command_exit_requested = 1; } /* fit_command() is in fit.c */ /* help_command() is below */ /* process the 'history' command */ void history_command() { #ifdef USE_READLINE c_token++; if (!END_OF_COMMAND && equals(c_token,"?")) { static char *search_str = NULL; /* string from command line to search for */ /* find and show the entries */ c_token++; if (isstring(c_token)) m_quote_capture(&search_str, c_token, c_token); else m_capture(&search_str, c_token, c_token); printf ("history ?%s\n", search_str); if (!history_find_all(search_str)) int_error(c_token,"not in history"); c_token++; } else if (!END_OF_COMMAND && equals(c_token,"!")) { const char *line_to_do = NULL; /* command returned by search */ c_token++; if (isanumber(c_token)) { int i = int_expression(); line_to_do = history_find_by_number(i); } else { char *search_str = NULL; /* string from command line to search for */ if (isstring(c_token)) m_quote_capture(&search_str, c_token, c_token); else m_capture(&search_str, c_token, c_token); line_to_do = history_find(search_str); free(search_str); } if (line_to_do == NULL) int_error(c_token, "not in history"); /* Add the command to the history. Note that history commands themselves are no longer added to the history. */ add_history((char *) line_to_do); printf(" Executing:\n\t%s\n", line_to_do); do_string(line_to_do); c_token++; } else { int n = 0; /* print only entries */ char *tmp; TBOOLEAN append = FALSE; /* rewrite output file or append it */ static char *name = NULL; /* name of the output file; NULL for stdout */ TBOOLEAN quiet = history_quiet; if (!END_OF_COMMAND && almost_equals(c_token,"q$uiet")) { /* option quiet to suppress history entry numbers */ quiet = TRUE; c_token++; } /* show history entries */ if (!END_OF_COMMAND && isanumber(c_token)) { n = int_expression(); } if ((tmp = try_to_get_string())) { free(name); name = tmp; if (!END_OF_COMMAND && almost_equals(c_token, "ap$pend")) { append = TRUE; c_token++; } } write_history_n(n, (quiet ? "" : name), (append ? "a" : "w")); } #else c_token++; int_warn(NO_CARET, "This copy of gnuplot was built without support for command history."); #endif /* defined(USE_READLINE) */ } #define REPLACE_ELSE(tok) \ do { \ int idx = token[tok].start_index; \ token[tok].length = 1; \ gp_input_line[idx++] = ';'; /* e */ \ gp_input_line[idx++] = ' '; /* l */ \ gp_input_line[idx++] = ' '; /* s */ \ gp_input_line[idx++] = ' '; /* e */ \ } while (0) /* Make a copy of an input line substring delimited by { and } */ static char * new_clause(int clause_start, int clause_end) { char *clause = gp_alloc(clause_end - clause_start, "clause"); memcpy(clause, &gp_input_line[clause_start+1], clause_end - clause_start); clause[clause_end - clause_start - 1] = '\0'; return clause; } /* * if / else command * * Aug 2020: * Restructured to handle if / else if / else sequences in a single routine. * Because the routine must handle recursion, the state flag (if_status) * is made a parameter rather than a global. */ static void if_else_command(ifstate if_state) { int clause_start, clause_end; int next_token; /* initial or recursive ("else if") if clause */ if (equals(c_token,"if")) { struct at_type *expr; if (!equals(++c_token, "(")) int_error(c_token, "expecting (expression)"); /* advance past if condition whether or not we evaluate it */ expr = temp_at(); if (equals(c_token,"{")) { next_token = find_clause(&clause_start, &clause_end); } else { /* pre-v5 syntax for "if" with no curly brackets */ old_if_command(expr); return; } if (if_state == IF_TRUE) { /* This means we are here recursively in an "else if" * following an "if" clause that was already executed. * Skip both the expression and the bracketed clause. */ c_token = next_token; } else if (TRUE || if_state == IF_INITIAL) { struct value condition; evaluate_at(expr, &condition); if (real(&condition) == 0) { if_state = IF_FALSE; c_token = next_token; } else { char *clause; if_state = IF_TRUE; clause = new_clause(clause_start, clause_end); begin_clause(); do_string_and_free(clause); end_clause(); if (iteration_early_exit()) c_token = num_tokens; else c_token = next_token; } } else { int_error(c_token, "unexpected if_state"); } } /* Done with "if" portion. Check for "else" */ if (equals(c_token,"else")) { c_token++; if (equals(c_token,"if")) { if_else_command(if_state); } else if (equals(c_token,"{")) { next_token = find_clause(&clause_start, &clause_end); if (if_state == IF_TRUE) { c_token = next_token; } else { char *clause; if_state = IF_TRUE; clause = new_clause(clause_start, clause_end); begin_clause(); do_string_and_free(clause); end_clause(); if (iteration_early_exit()) c_token = num_tokens; else c_token = next_token; } if_open_for_else = FALSE; } else { int_error(c_token, "expecting bracketed else clause"); } } else { /* There was no "else" on this line but we might see it on another line */ if_open_for_else = !(if_state == IF_TRUE); } return; } /* * The original if_command and else_command become wrappers */ void if_command() { if_else_command(IF_INITIAL); return; } void else_command() { if_else_command(if_open_for_else ? IF_FALSE : IF_TRUE); return; } /* * Old if/else syntax (no curly braces) is confined to a single input line. * The rest of the line is *always* consumed. */ static void old_if_command(struct at_type *expr) { struct value condition; char *if_start, *if_end; char *else_start = NULL; if (clause_depth > 0) int_error(c_token,"Old-style if/else statement encountered inside brackets"); evaluate_at(expr, &condition); /* find start and end of the "if" part */ if_start = &gp_input_line[ token[c_token].start_index ]; while ((c_token < num_tokens) && !equals(c_token,"else")) c_token++; if (equals(c_token,"else")) { if_end = &gp_input_line[ token[c_token].start_index - 1 ]; *if_end = '\0'; else_start = &gp_input_line[ token[c_token].start_index + token[c_token].length ]; } if (real(&condition) != 0.0) do_string(if_start); else if (else_start) do_string(else_start); c_token = num_tokens = 0; /* discard rest of line */ } /* process commands of the form 'do for [i=1:N] ...' */ void do_command() { t_iterator *do_iterator; int do_start, do_end; int end_token; char *clause; c_token++; do_iterator = check_for_iteration(); if (forever_iteration(do_iterator)) { cleanup_iteration(do_iterator); int_error(c_token-2, "unbounded iteration not accepted here"); } if (!equals(c_token,"{")) { cleanup_iteration(do_iterator); int_error(c_token,"expecting {do-clause}"); } end_token = find_clause(&do_start, &do_end); clause = new_clause(do_start, do_end); begin_clause(); iteration_depth++; /* Sometimes the start point of a nested iteration is not within the * limits for all levels of nesting. In this case we need to advance * through the iteration to find the first good set of indices. * If we don't find one, forget the whole thing. */ if (empty_iteration(do_iterator) && !next_iteration(do_iterator)) { strcpy(clause, ";"); } do { requested_continue = FALSE; do_string(clause); if (command_exit_requested != 0) requested_break = TRUE; if (requested_break) break; } while (next_iteration(do_iterator)); iteration_depth--; /* FIXME: * If any of the above exited via int_error() then this cleanup * never happens and we leak memory for both clause and for the * iterator itself. But do_iterator cannot be static or global * because do_command() can recurse. */ free(clause); end_clause(); c_token = end_token; do_iterator = cleanup_iteration(do_iterator); requested_break = FALSE; requested_continue = FALSE; } /* process commands of the form 'while (foo) {...}' */ /* FIXME: For consistency there should be an iterator associated * with this statement. */ void while_command() { int do_start, do_end; char *clause; int save_token, end_token; double exprval; c_token++; save_token = c_token; exprval = real_expression(); if (!equals(c_token,"{")) int_error(c_token,"expecting {while-clause}"); end_token = find_clause(&do_start, &do_end); clause = new_clause(do_start, do_end); begin_clause(); iteration_depth++; while (exprval != 0) { requested_continue = FALSE; do_string(clause); if (command_exit_requested) requested_break = TRUE; if (requested_break) break; c_token = save_token; exprval = real_expression(); }; iteration_depth--; end_clause(); free(clause); c_token = end_token; requested_break = FALSE; requested_continue = FALSE; } /* * set link [x2|y2] {via {inverse }} * set nonlinear via inverse * unset link [x2|y2] * unset nonlinear */ void link_command() { AXIS *primary_axis = NULL; AXIS *secondary_axis = NULL; TBOOLEAN linked = FALSE; int command_token = c_token; /* points to "link" or "nonlinear" */ c_token++; /* Set variable name accepatable for the via/inverse functions */ strcpy(c_dummy_var[0], "x"); strcpy(c_dummy_var[1], "y"); if (equals(c_token, "z") || equals(c_token, "cb")) strcpy(c_dummy_var[0], "z"); if (equals(c_token, "r")) strcpy(c_dummy_var[0], "r"); /* * "set nonlinear" currently supports axes x x2 y y2 z r cb */ if (equals(command_token,"nonlinear")) { AXIS_INDEX axis; if ((axis = lookup_table(axisname_tbl, c_token)) >= 0) secondary_axis = &axis_array[axis]; else int_error(c_token,"not a valid nonlinear axis"); primary_axis = get_shadow_axis(secondary_axis); /* Trap attempt to set an already-linked axis to nonlinear */ /* This catches the sequence "set link y; set nonlinear y2" */ if (secondary_axis->linked_to_primary && secondary_axis->linked_to_primary->index > 0) int_error(NO_CARET,"must unlink axis before setting it to nonlinear"); if (secondary_axis->linked_to_secondary && secondary_axis->linked_to_secondary->index > 0) int_error(NO_CARET,"must unlink axis before setting it to nonlinear"); /* Clear previous log status */ secondary_axis->log = FALSE; secondary_axis->ticdef.logscaling = FALSE; /* * "set link" applies to either x|x2 or y|y2 * Flag the axes as being linked, and copy the range settings * from the primary axis into the linked secondary axis */ } else { if (almost_equals(c_token,"x$2")) { primary_axis = &axis_array[FIRST_X_AXIS]; secondary_axis = &axis_array[SECOND_X_AXIS]; } else if (almost_equals(c_token,"y$2")) { primary_axis = &axis_array[FIRST_Y_AXIS]; secondary_axis = &axis_array[SECOND_Y_AXIS]; } else { int_error(c_token,"expecting x2 or y2"); } /* This catches the sequence "set nonlinear x; set link x2" */ if (primary_axis->linked_to_primary) int_error(NO_CARET, "You must clear nonlinear x or y before linking it"); /* This catches the sequence "set nonlinear x2; set link x2" */ if (secondary_axis->linked_to_primary && secondary_axis->linked_to_primary->index <= 0) int_error(NO_CARET, "You must clear nonlinear x2 or y2 before linking it"); } c_token++; /* "unset link {x|y}" command */ if (equals(command_token-1,"unset")) { primary_axis->linked_to_secondary = NULL; if (secondary_axis->linked_to_primary == NULL) /* It wasn't linked anyhow */ return; else secondary_axis->linked_to_primary = NULL; /* can't return yet because we need to free link_udf->at */ linked = FALSE; } else { linked = TRUE; } /* Initialize the action tables for the mapping function[s] */ if (!primary_axis->link_udf) { primary_axis->link_udf = gp_alloc(sizeof(udft_entry),"link_at"); memset(primary_axis->link_udf, 0, sizeof(udft_entry)); } if (!secondary_axis->link_udf) { secondary_axis->link_udf = gp_alloc(sizeof(udft_entry),"link_at"); memset(secondary_axis->link_udf, 0, sizeof(udft_entry)); } if (equals(c_token,"via")) { parse_link_via(secondary_axis->link_udf); if (almost_equals(c_token,"inv$erse")) { parse_link_via(primary_axis->link_udf); } else { int_warn(c_token,"inverse mapping function required"); linked = FALSE; } } else if (equals(command_token,"nonlinear") && linked) { int_warn(c_token,"via mapping function required"); linked = FALSE; } if (equals(command_token,"nonlinear") && linked) { /* Save current user-visible axis range (note reversed order!) */ struct udft_entry *temp = primary_axis->link_udf; primary_axis->link_udf = secondary_axis->link_udf; secondary_axis->link_udf = temp; secondary_axis->linked_to_primary = primary_axis; primary_axis->linked_to_secondary = secondary_axis; clone_linked_axes(secondary_axis, primary_axis); } else if (linked) { /* Clone the range information */ secondary_axis->linked_to_primary = primary_axis; primary_axis->linked_to_secondary = secondary_axis; clone_linked_axes(primary_axis, secondary_axis); } else { free_at(secondary_axis->link_udf->at); secondary_axis->link_udf->at = NULL; free_at(primary_axis->link_udf->at); primary_axis->link_udf->at = NULL; /* Shouldn't be necessary, but it doesn't hurt */ primary_axis->linked_to_secondary = NULL; secondary_axis->linked_to_primary = NULL; } if (secondary_axis->index == POLAR_AXIS) rrange_to_xy(); } void load_command() { load_or_call_command( FALSE ); } void call_command() { load_or_call_command( TRUE ); } /* The load and call commands are identical except that * call may be followed by a bunch of command line arguments */ static void load_or_call_command( TBOOLEAN call ) { c_token++; if (equals(c_token, "$") && isletter(c_token+1) && !equals(c_token+2,"[")) { /* "load" a datablock rather than a file */ /* datablock_name will eventually be freed by lf_pop() */ char *datablock_name = gp_strdup(parse_datablock_name()); load_file(NULL, datablock_name, call ? 7 : 6); } else { /* These need to be local so that recursion works. */ /* They will eventually be freed by lf_pop(). */ FILE *fp; char *save_file = try_to_get_string(); if (!save_file) int_error(c_token, "expecting filename"); gp_expand_tilde(&save_file); if (!call && !strcmp(save_file, "-")) fp = stdout; else fp = loadpath_fopen(save_file, "r"); load_file(fp, save_file, call ? 2 : 1); } } /* null command */ void null_command() { return; } /* Clauses enclosed by curly brackets: * do for [i = 1:N] { a; b; c; } * if () { * line1; * line2; * } else { * ... * } */ /* Find the start and end character positions within gp_input_line * bounding a clause delimited by {...}. * Assumes that c_token indexes the opening left curly brace. * Returns the index of the first token after the closing curly brace. */ int find_clause(int *clause_start, int *clause_end) { int i, depth; *clause_start = token[c_token].start_index; for (i=++c_token, depth=1; ilocal_variables = TRUE; } /* Create new local variable with this name */ if (array_token) { c_token = array_token; array_command(); if (udv && udv->udv_value.type == ARRAY) udv->udv_value.v.value_array[0].type = LOCAL_ARRAY; } else { define(); } } /* helper routine for local_command and for setting up the * initial state of function block evaluation */ void shadow_one_variable(struct udvt_entry *udv) { struct udvt_entry *save_udv; struct value save_value; int save_name_len; char *save_name; save_value = udv->udv_value; udv->udv_value.type = NOTDEFINED; /* Create a shadow variable to hold the original state */ save_name_len = strlen(udv->udv_name) + strlen("GPLOCAL_xxx_") + 1; save_name = gp_alloc(save_name_len, NULL); snprintf(save_name, save_name_len, "GPLOCAL_%03d_%s", lf_head->depth, udv->udv_name); save_udv = add_udv_by_name(save_name); free(save_name); /* If this is a duplicate "local" instance at the same depth, * the previous value at this depth is dropped. */ if (save_udv->udv_value.type != NOTDEFINED) { int_warn(NO_CARET, "Duplicate 'local' declaration for %s\n", udv->udv_name); free_value(&save_value); return; } /* If there really was such a variable that is now shadowed, * save its original value. Otherwise the saved state is NOTDEFINED. */ save_udv->udv_value = save_value; } /* helper routine to multiplex mouse event handling with a timed pause command */ void timed_pause(double sleep_time) { #if defined(HAVE_USLEEP) && defined(USE_MOUSE) && !defined(_WIN32) if (term->waitforinput) /* If the terminal supports it */ while (sleep_time > 0.05) { /* we poll 20 times a second */ usleep(50000); /* Sleep for 50 msec */ check_for_mouse_events(); sleep_time -= 0.05; } usleep((useconds_t)(sleep_time * 1e6)); check_for_mouse_events(); #else GP_SLEEP(sleep_time); #endif } /* process the 'pause' command */ #define EAT_INPUT_WITH(slurp) do {int junk=0; do {junk=slurp;} while (junk != EOF && junk != '\n');} while (0) void pause_command() { TBOOLEAN text = FALSE; double sleep_time; static char *buf = NULL; c_token++; #ifdef USE_MOUSE paused_for_mouse = 0; if (equals(c_token, "mouse")) { sleep_time = -1; c_token++; if (mouse_setting.on && term) { struct udvt_entry *current; int end_condition = 0; while (!(END_OF_COMMAND)) { if (almost_equals(c_token, "key$press")) { end_condition |= PAUSE_KEYSTROKE; c_token++; } else if (equals(c_token, ",")) { c_token++; } else if (equals(c_token, "any")) { end_condition |= PAUSE_ANY; c_token++; } else if (equals(c_token, "button1")) { end_condition |= PAUSE_BUTTON1; c_token++; } else if (equals(c_token, "button2")) { end_condition |= PAUSE_BUTTON2; c_token++; } else if (equals(c_token, "button3")) { end_condition |= PAUSE_BUTTON3; c_token++; } else if (equals(c_token, "close")) { end_condition |= PAUSE_WINCLOSE; c_token++; } else break; } if (end_condition) paused_for_mouse = end_condition; else paused_for_mouse = PAUSE_CLICK; /* Set the pause mouse return codes to -1 */ current = add_udv_by_name("MOUSE_KEY"); Ginteger(&current->udv_value,-1); current = add_udv_by_name("MOUSE_BUTTON"); Ginteger(&current->udv_value,-1); } else { int_warn(NO_CARET, "Mousing not active"); while (!END_OF_COMMAND) c_token++; } } else #endif sleep_time = real_expression(); if (END_OF_COMMAND) { free(buf); /* remove the previous message */ buf = gp_strdup("paused"); /* default message, used in GUI pause dialog */ } else { char *tmp = try_to_get_string(); if (!tmp) int_error(c_token, "expecting string"); else { free(buf); buf = tmp; #ifdef _WIN32 if (sleep_time >= 0) fputs(buf, stderr); #elif defined(OS2) if (strcmp(term->name, "pm") != 0 || sleep_time >= 0) fputs(buf, stderr); #else /* Not _WIN32 or OS2 */ fputs(buf, stderr); #endif text = TRUE; } } if (sleep_time < 0) { #if defined(_WIN32) ctrlc_flag = FALSE; # if defined(WGP_CONSOLE) && defined(USE_MOUSE) if (!paused_for_mouse || !MousableWindowOpened()) { int junk = 0; if (buf) { /* Use of fprintf() triggers a bug in MinGW + SJIS encoding */ fputs(buf, stderr); fputc('\n', stderr); } /* Note: cannot use EAT_INPUT_WITH here * FIXME: probably term->waitforinput is always correct, not just for qt */ if (!strcmp(term->name,"qt")) term->waitforinput(0); else do { junk = getch(); if (ctrlc_flag) bail_to_command_line(); } while (junk != EOF && junk != '\n' && junk != '\r'); } else /* paused_for_mouse */ # endif /* !WGP_CONSOLE */ { if (!Pause(buf)) /* returns false if Ctrl-C or Cancel was pressed */ bail_to_command_line(); } #elif defined(OS2) && defined(USE_MOUSE) if ((strcmp(term->name, "pm") == 0) || (strcmp(term->name, "x11") == 0)) { ULONG u; if (paused_for_mouse && /* only pause for mouse if window is actually open */ ((strcmp(term->name, "pm") != 0) || PM_is_connected())) { /* Only print a message if there is one given on the command line. */ if (text) fputs(buf, stderr); while (paused_for_mouse) { /* wait for mouse event */ DosWaitEventSem(semInputReady, SEM_INDEFINITE_WAIT); DosResetEventSem(semInputReady, &u); os2_ipc_dispatch_event(); } if (text) fputc('\n', stderr); } else { /* If input is redirected or if the x11 terminal is active, we don't use a dialog/menu for pausing. */ pause_internal = !isatty(fileno(stdin)) || (strcmp(term->name, "x11") == 0); thread_pause_RetCode = -1; _beginthread(thread_pause, NULL, 32768, text ? buf : NULL); do { /* wait until pause is done or gnupmdrv makes shared mem available */ DosWaitEventSem(semInputReady, SEM_INDEFINITE_WAIT); DosResetEventSem(semInputReady, &u); if (thread_pause_RetCode < 0) os2_ipc_dispatch_event(); } while (thread_pause_RetCode < 0); if (!isatty(fileno(stdin)) && text) fputc('\n', stderr); if (thread_pause_RetCode == 0) { /* if (!CallFromRexx) * would help to stop REXX programs w/o raising an error message * in RexxInterface() ... */ bail_to_command_line(); } } } else { /* NOT x11 or pm terminal, so need to handle mouse events, just wait for input */ EAT_INPUT_WITH(fgetc(stdin)); fputc('\n', stderr); } #else /* !(_WIN32 || OS2) */ # ifdef USE_MOUSE if (term && term->waitforinput) { int terminating_char = term->waitforinput(0); if (isalnum(terminating_char)) ungetc(terminating_char, stdin); } else # endif /* USE_MOUSE */ # ifdef MSDOS { int junk; /* cannot use EAT_INPUT_WITH here */ do { # ifdef __DJGPP__ /* We use getkey() since with DJGPP 2.05 and gcc 7.2, getchar() requires two keystrokes. */ junk = getkey(); # else junk = getch(); # endif /* Check if Ctrl-C was pressed */ if (junk == 0x03) bail_to_command_line(); } while (junk != EOF && junk != '\n' && junk != '\r'); fputc('\n', stderr); } # else EAT_INPUT_WITH(fgetc(stdin)); # endif #endif /* !(_WIN32 || OS2) */ } if (sleep_time > 0) timed_pause(sleep_time); if (text && sleep_time >= 0) fputc('\n', stderr); screen_ok = FALSE; } /* process the 'plot' command */ void plot_command() { plot_token = c_token++; plotted_data_from_stdin = FALSE; refresh_nplots = 0; plot_iterator = cleanup_iteration(plot_iterator); SET_CURSOR_WAIT; #ifdef USE_MOUSE plot_mode(MODE_PLOT); add_udv_by_name("MOUSE_X")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_Y")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_X2")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_Y2")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_BUTTON")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_SHIFT")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_ALT")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_CTRL")->udv_value.type = NOTDEFINED; #endif if (evaluate_inside_functionblock && inside_plot_command) int_error(NO_CARET, "plot command not available in this context"); inside_plot_command = TRUE; plotrequest(); /* Clear "hidden" flag for any plots that may have been toggled off */ if (term->modify_plots) term->modify_plots(MODPLOTS_SET_VISIBLE, -1); inside_plot_command = FALSE; SET_CURSOR_ARROW; } void print_set_output(char *name, TBOOLEAN datablock, TBOOLEAN append_p) { if (print_out && print_out != stderr && print_out != stdout) { #ifdef PIPES if (print_out_name[0] == '|') { if (0 > pclose(print_out)) perror(print_out_name); } else #endif if (0 > fclose(print_out)) perror(print_out_name); print_out = stderr; } free(print_out_name); print_out_name = NULL; print_out_var = NULL; if (! name) { print_out = stderr; return; } if (strcmp(name, "-") == 0) { print_out = stdout; return; } #ifdef PIPES if (name[0] == '|') { restrict_popen(); print_out = popen(name + 1, "w"); if (!print_out) perror(name); else print_out_name = name; return; } #endif if (!datablock) { print_out = fopen(name, append_p ? "a" : "w"); if (!print_out) { perror(name); return; } } else { /* Make sure we will not overwrite the current input. */ if (called_from(name)) { free(name); int_error(NO_CARET, "print output must not overwrite input"); } /* We already know the name begins with $, * so the udv is either a new empty variable (no need to clear) * an existing datablock (append or clear according to flag) * or (probably by mistake) a voxel grid (clear before use). */ print_out_var = add_udv_by_name(name); if (!append_p) { gpfree_datablock(&print_out_var->udv_value); gpfree_functionblock(&print_out_var->udv_value); } if (print_out_var->udv_value.type != DATABLOCK) { free_value(&print_out_var->udv_value); gpfree_vgrid(print_out_var); print_out_var->udv_value.type = DATABLOCK; print_out_var->udv_value.v.data_array = NULL; } } print_out_name = name; } char * print_show_output() { if (print_out_name) return print_out_name; if (print_out == stdout) return ""; if (!print_out || print_out == stderr || !print_out_name) return ""; return print_out_name; } /* 'printerr' is the same as 'print' except that output is always to stderr */ void printerr_command() { FILE *save_print_out = print_out; struct udvt_entry *save_print_out_var = print_out_var; print_out = stderr; print_out_var = NULL; print_command(); print_out_var = save_print_out_var; print_out = save_print_out; } /* process the 'print' command */ void print_command() { struct value a; int save_token; /* field separator is not needed for the first entry */ TBOOLEAN need_space = FALSE; char *field_sep = print_sep ? print_sep : " "; char *dataline = NULL; size_t size = 256; size_t len = 0; if (!print_out) print_out = stderr; if (print_out_var != NULL) { /* print to datablock */ dataline = (char *) gp_alloc(size, "dataline"); *dataline = NUL; } screen_ok = FALSE; do { ++c_token; if (equals(c_token, "$") && isletter(c_token+1) && !equals(c_token+2,"[") && !equals(c_token+2,"(")) { char **line; char *datablock_name = parse_datablock_name(); struct udvt_entry *block = get_udv_by_name(datablock_name); if (!block) int_error(c_token, "no block named %s", datablock_name); #ifdef USE_FUNCTIONBLOCKS if (block->udv_value.type == FUNCTIONBLOCK && (print_out_var == NULL)) { line = block->udv_value.v.functionblock.parnames; fprintf(print_out, "function %s( ", datablock_name); while (line && *line) { fprintf(print_out, "%s ", *line); line++; } fprintf(print_out, ")\n"); line = block->udv_value.v.functionblock.data_array; } else #endif /* USE_FUNCTIONBLOCKS */ line = block->udv_value.v.data_array; /* Printing a datablock into itself would cause infinite recursion */ if (print_out_var && !strcmp(datablock_name, print_out_name)) continue; if (need_space && !print_out_var) fprintf(print_out, "\n"); while (line && *line) { if (print_out_var != NULL) append_to_datablock(&print_out_var->udv_value, strdup(*line)); else fprintf(print_out, "%s\n", *line); line++; } need_space = FALSE; continue; } /* Prepare for possible iteration */ save_token = c_token; print_iterator = check_for_iteration(); if (empty_iteration(print_iterator)) { const_express(&a); print_iterator = cleanup_iteration(print_iterator); continue; } if (forever_iteration(print_iterator)) { print_iterator = cleanup_iteration(print_iterator); int_error(save_token, "unbounded iteration not accepted here"); } save_token = c_token; ITERATE: /* All entries other than the first one on a line */ if (need_space) { if (dataline != NULL) len = strappend(&dataline, &size, len, field_sep); else fputs(field_sep, print_out); } need_space = TRUE; const_express(&a); if (a.type == STRING) { if (dataline != NULL) len = strappend(&dataline, &size, len, a.v.string_val); else fputs(a.v.string_val, print_out); gpfree_string(&a); } else if (a.type == ARRAY) { struct value *array = a.v.value_array; if (dataline != NULL) { int i; int arraysize = array[0].v.int_val; len = strappend(&dataline, &size, len, "["); for (i = 1; i <= arraysize; i++) { if (array[i].type != NOTDEFINED) len = strappend(&dataline, &size, len, value_to_str(&array[i], TRUE)); if (i < arraysize) len = strappend(&dataline, &size, len, ","); } len = strappend(&dataline, &size, len, "]"); } else { save_array_content(print_out, array); } if (array[0].type == TEMP_ARRAY) gpfree_array(&a); a.type = NOTDEFINED; } else { if (dataline != NULL) len = strappend(&dataline, &size, len, value_to_str(&a, FALSE)); else disp_value(print_out, &a, FALSE); } /* Any other value types besides STRING and ARRAY that need memory freed? */ if (next_iteration(print_iterator)) { c_token = save_token; goto ITERATE; } else { print_iterator = cleanup_iteration(print_iterator); } } while (!END_OF_COMMAND && equals(c_token, ",")); if (dataline) { if (print_out_var == NULL) int_error(NO_CARET, "print destination was clobbered"); append_multiline_to_datablock(&print_out_var->udv_value, dataline); } else { putc('\n', print_out); fflush(print_out); } } /* process the 'pwd' command */ void pwd_command() { char *save_file = NULL; save_file = gp_alloc(PATH_MAX, "print current dir"); if (GP_GETCWD(save_file, PATH_MAX) == NULL) fprintf(stderr, "\n"); else fprintf(stderr, "%s\n", save_file); free(save_file); c_token++; } /* EAM April 2007 * The "refresh" command replots the previous graph without going back to read * the original data. This allows zooming or other operations on data that was * only transiently available in the input stream. */ void refresh_command() { c_token++; refresh_request(); } void refresh_request() { AXIS_INDEX axis; if (evaluate_inside_functionblock && inside_plot_command) int_error(NO_CARET, "refresh command not available in this context"); inside_plot_command = TRUE; if ( ((first_plot == NULL) && (refresh_ok == E_REFRESH_OK_2D)) || ((first_3dplot == NULL) && (refresh_ok == E_REFRESH_OK_3D)) || (!*replot_line && (refresh_ok == E_REFRESH_NOT_OK)) ) int_error(NO_CARET, "no active plot; cannot refresh"); if (refresh_ok == E_REFRESH_NOT_OK) { int_warn(NO_CARET, "cannot refresh from this state. trying full replot"); replotrequest(); return; } /* The margins from "set offset" were already applied; * don't reapply them here */ retain_offsets = TRUE; /* Restore the axis range/scaling state from original plot * Dima Kogan April 2018 */ for (axis = 0; axis < NUMBER_OF_MAIN_VISIBLE_AXES; axis++) { AXIS *this_axis = &axis_array[axis]; if ((this_axis->set_autoscale & AUTOSCALE_MIN) && (this_axis->writeback_min < VERYLARGE)) this_axis->set_min = this_axis->writeback_min; else this_axis->min = this_axis->set_min; if ((this_axis->set_autoscale & AUTOSCALE_MAX) && (this_axis->writeback_max > -VERYLARGE)) this_axis->set_max = this_axis->writeback_max; else this_axis->max = this_axis->set_max; if (this_axis->linked_to_secondary) clone_linked_axes(this_axis, this_axis->linked_to_secondary); else if (this_axis->linked_to_primary) { if ((this_axis->linked_to_primary->autoscale & AUTOSCALE_BOTH) != AUTOSCALE_BOTH) clone_linked_axes(this_axis, this_axis->linked_to_primary); } } if (refresh_ok == E_REFRESH_OK_2D) { refresh_bounds(first_plot, refresh_nplots); do_plot(first_plot, refresh_nplots); update_gpval_variables(1); } else if (refresh_ok == E_REFRESH_OK_3D) { refresh_3dbounds(first_3dplot, refresh_nplots); do_3dplot(first_3dplot, refresh_nplots, 0); update_gpval_variables(1); } else int_error(NO_CARET, "Internal error - refresh of unknown plot type"); inside_plot_command = FALSE; } /* process the 'replot' command */ void replot_command() { if (!*replot_line) int_error(c_token, "no previous plot"); if (volatile_data && (refresh_ok != E_REFRESH_NOT_OK) && !replot_disabled) { FPRINTF((stderr,"volatile_data %d refresh_ok %d plotted_data_from_stdin %d\n", volatile_data, refresh_ok, plotted_data_from_stdin)); refresh_command(); return; } /* Disable replot for some reason; currently used by the mouse/hotkey capable terminals to avoid replotting when some data come from stdin, i.e. when plotted_data_from_stdin==1 after plot "-". */ if (replot_disabled) { replot_disabled = FALSE; bail_to_command_line(); /* be silent --- don't mess the screen */ } if (!term) /* unknown terminal */ int_error(c_token, "use 'set term' to set terminal type first"); c_token++; SET_CURSOR_WAIT; if (term->flags & TERM_INIT_ON_REPLOT) term->init(); replotrequest(); SET_CURSOR_ARROW; } /* process the 'reread' command */ void reread_command() { FILE *fp = lf_top(); if (evaluate_inside_functionblock) int_error(NO_CARET, "reread command not possible in a function block"); if (fp != (FILE *) NULL) rewind(fp); c_token++; } #ifdef USE_FUNCTIONBLOCKS /* 'return ' clears or sets a return value and then acts * like 'exit' to return to the caller immediately. The only way that * anything sees the return value is if the 'return' statement is * executed inside a function block. */ void return_command() { c_token++; gpfree_string(&eval_return_value); if (!END_OF_COMMAND) { const_express(&eval_return_value); if (eval_return_value.type == ARRAY) { make_array_permanent(&eval_return_value); eval_return_value.v.value_array[0].type = TEMP_ARRAY; } } command_exit_requested = 1; } #else /* USE_FUNCTIONBLOCKS */ void return_command() {} #endif /* USE_FUNCTIONBLOCKS */ /* process the 'save' command */ void save_command() { FILE *fp; char *save_file = NULL; TBOOLEAN append = FALSE; int what; c_token++; what = lookup_table(&save_tbl[0], c_token); switch (what) { case SAVE_FUNCS: case SAVE_SET: case SAVE_TERMINAL: case SAVE_VARS: case SAVE_FIT: case SAVE_DATABLOCKS: c_token++; break; default: break; } save_file = try_to_get_string(); if (!save_file) int_error(c_token, "expecting filename"); if (equals(c_token, "append")) { append = TRUE; c_token++; } #ifdef PIPES if (save_file[0]=='|') { restrict_popen(); fp = popen(save_file+1,"w"); } else #endif { gp_expand_tilde(&save_file); #ifdef _WIN32 fp = !strcmp(save_file,"-") ? stdout : loadpath_fopen(save_file, append?"a":"w"); #else fp = !strcmp(save_file,"-") ? stdout : fopen(save_file, append?"a":"w"); #endif } if (!fp) os_error(c_token, "Cannot open save file"); switch (what) { case SAVE_FUNCS: save_functions(fp); break; case SAVE_SET: save_set(fp); break; case SAVE_TERMINAL: save_term(fp); break; case SAVE_VARS: save_variables(fp); break; case SAVE_FIT: save_fit(fp); break; case SAVE_DATABLOCKS: save_datablocks(fp); break; default: save_all(fp); } if (stdout != fp) { #ifdef PIPES if (save_file[0] == '|') (void) pclose(fp); else #endif (void) fclose(fp); } free(save_file); } /* process the 'screendump' command */ void screendump_command() { c_token++; #ifdef _WIN32 screen_dump(); #else fputs("screendump not implemented\n", stderr); #endif } /* set_command() is in set.c */ /* show_command() is in show.c */ /* 'shell' command is processed by do_shell(), see below */ void shell_command() { if (evaluate_inside_functionblock) int_error(NO_CARET, "bare shell commands not accepted in a function block"); do_shell(); } /* process the 'splot' command */ void splot_command() { plot_token = c_token++; plotted_data_from_stdin = FALSE; refresh_nplots = 0; plot_iterator = cleanup_iteration(plot_iterator); SET_CURSOR_WAIT; #ifdef USE_MOUSE plot_mode(MODE_SPLOT); add_udv_by_name("MOUSE_X")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_Y")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_X2")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_Y2")->udv_value.type = NOTDEFINED; add_udv_by_name("MOUSE_BUTTON")->udv_value.type = NOTDEFINED; #endif if (evaluate_inside_functionblock && inside_plot_command) int_error(NO_CARET, "splot command not available in this context"); inside_plot_command = TRUE; plot3drequest(); /* Clear "hidden" flag for any plots that may have been toggled off */ if (term->modify_plots) term->modify_plots(MODPLOTS_SET_VISIBLE, -1); inside_plot_command = FALSE; SET_CURSOR_ARROW; } /* process the 'stats' command */ void stats_command() { #ifdef USE_STATS if (evaluate_inside_functionblock && inside_plot_command) int_error(NO_CARET, "stats command not available in this context"); inside_plot_command = TRUE; statsrequest(); inside_plot_command = FALSE; #else int_error(NO_CARET,"This copy of gnuplot was not configured with support for the stats command"); #endif } /* process the 'system' command */ void system_command() { char *cmd; ++c_token; cmd = try_to_get_string(); do_system(cmd); free(cmd); } /* * process the 'test palette' command * 1) Write a sequence of plot commands + set commands into a temp file * 2) Create a datablock with palette values * 3) Load the temp file to plot from the datablock * The set commands then act to restore the initial state */ static void test_palette_subcommand() { enum {test_palette_colors = 256}; struct udvt_entry *datablock; char *save_replot_line; TBOOLEAN save_is_3d_plot; int i; static const char pre1[] = "\ reset;\ uns border; se tics scale 0;\ se cbtic 0,0.1,1 mirr format '' scale 1;\ se xr[0:1];se yr[0:1];se zr[0:1];se cbr[0:1];\ set colorbox hor user orig 0.05,0.02 size 0.925,0.12;"; static const char pre2[] = "\ se lmarg scre 0.05;se rmarg scre 0.975; se bmarg scre 0.22; se tmarg scre 0.86;\ se grid; se xtics 0,0.1;se ytics 0,0.1;\ se key top right at scre 0.975,0.975 horizontal \ title 'R,G,B profiles of the current color palette';"; static const char pre3[] = "\ p NaN lc palette notit,\ $PALETTE u 1:2 t 'red' w l lt 1 lc rgb 'red',\ '' u 1:3 t 'green' w l lt 1 lc rgb 'green',\ '' u 1:4 t 'blue' w l lt 1 lc rgb 'blue',\ '' u 1:5 t 'NTSC' w l lt 1 lc rgb 'black'\ \n"; FILE *f = tmpfile(); #if defined(_MSC_VER) || defined(__MINGW32__) /* On Vista/Windows 7 tmpfile() fails. */ if (!f) { char buf[PATH_MAX]; /* We really want the "ANSI" version */ GetTempPathA(sizeof(buf), buf); strcat(buf, "gnuplot-pal.tmp"); f = fopen(buf, "w+"); } #endif while (!END_OF_COMMAND) c_token++; if (!f) int_error(NO_CARET, "cannot write temporary file"); /* Store R/G/B/Int curves in a datablock */ datablock = add_udv_by_name("$PALETTE"); free_value(&datablock->udv_value); datablock->udv_value.type = DATABLOCK; datablock->udv_value.v.data_array = NULL; /* Part of the purpose for writing these values into a datablock */ /* is so that the user can read them back if desired. But data */ /* will be read back using the current numeric locale, so for */ /* consistency we must also use the locale when creating it. */ set_numeric_locale(); for (i = 0; i < test_palette_colors; i++) { char dataline[64]; rgb_color rgb; double ntsc; double z = (double)i / (test_palette_colors - 1); double gray = (sm_palette.positive == SMPAL_NEGATIVE) ? 1. - z : z; rgb1_from_gray(gray, &rgb); ntsc = 0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b; sprintf(dataline, "%0.4f %0.4f %0.4f %0.4f %0.4f %c", z, rgb.r, rgb.g, rgb.b, ntsc, '\0'); append_to_datablock(&datablock->udv_value, strdup(dataline)); } reset_numeric_locale(); /* commands to setup the test palette plot */ enable_reset_palette = 0; save_replot_line = gp_strdup(replot_line); save_is_3d_plot = is_3d_plot; fputs(pre1, f); fputs(pre2, f); fputs(pre3, f); /* save current gnuplot 'set' status because of the tricky sets * for our temporary testing plot. */ save_set(f); save_pixmaps(f); /* execute all commands from the temporary file */ rewind(f); load_file(f, NULL, 1); /* note: it does fclose(f) */ /* enable reset_palette() and restore replot line */ enable_reset_palette = 1; free(replot_line); replot_line = save_replot_line; is_3d_plot = save_is_3d_plot; } /* process the 'test' command */ void test_command() { int what; int save_token = c_token++; if (!term) /* unknown terminal */ int_error(c_token, "use 'set term' to set terminal type first"); what = lookup_table(&test_tbl[0], c_token); switch (what) { default: if (!END_OF_COMMAND) int_error(c_token, "unrecognized test option"); /* otherwise fall through to test_term */ case TEST_TERMINAL: test_term(); break; case TEST_PALETTE: test_palette_subcommand(); break; } /* prevent annoying error messages if there was no previous plot */ /* and the "test" window is resized. */ if (!replot_line || !(*replot_line)) { m_capture( &replot_line, save_token, c_token ); } } /* toggle a single plot on/off from the command line * (only possible for qt, wxt, x11, win) */ void toggle_command() { int plotno = -1; char *plottitle = NULL; TBOOLEAN foundit = FALSE; c_token++; if (equals(c_token, "all")) { c_token++; } else if ((plottitle = try_to_get_string()) != NULL) { struct curve_points *plot; int last = strlen(plottitle) - 1; if (refresh_ok == E_REFRESH_OK_2D) plot = first_plot; else if (refresh_ok == E_REFRESH_OK_3D) plot = (struct curve_points *)first_3dplot; else plot = NULL; if (last >= 0) { for (plotno = 0; plot != NULL; plot = plot->next, plotno++) { if (plot->title) if (!strcmp(plot->title, plottitle) || (plottitle[last] == '*' && !strncmp(plot->title, plottitle, last))) { foundit = TRUE; break; } } } free(plottitle); if (!foundit) { int_warn(NO_CARET,"Did not find a plot with that title"); return; } } else { plotno = int_expression() - 1; } if (term->modify_plots) term->modify_plots(MODPLOTS_INVERT_VISIBILITIES, plotno); } void update_command() { int_error(NO_CARET, "DEPRECATED command 'update', please use 'save fit' instead"); } /* the "import" command is only implemented if support is configured for */ /* using functions from external shared objects as plugins. */ void import_command() { int start_token = c_token; #ifdef HAVE_EXTERNAL_FUNCTIONS struct udft_entry *udf; int dummy_num = 0; char save_dummy[MAX_NUM_VAR][MAX_ID_LEN+1]; if (!equals(++c_token + 1, "(")) int_error(c_token, "Expecting function template"); memcpy(save_dummy, c_dummy_var, sizeof(save_dummy)); 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, ",")) int_error(c_token + 1, "function contains too many parameters"); if (!equals(c_token++, ")")) int_error(c_token, "missing ')'"); if (!equals(c_token, "from")) int_error(c_token, "Expecting 'from '"); c_token++; udf = dummy_func = add_udf(start_token+1); udf->dummy_num = dummy_num; free_at(udf->at); /* In case there was a previous function by this name */ udf->at = external_at(udf->udf_name); memcpy(c_dummy_var, save_dummy, sizeof(save_dummy)); dummy_func = NULL; /* dont let anyone else use our workspace */ if (!udf->at) int_error(NO_CARET, "failed to load external function"); /* Don't copy the definition until we know it worked */ m_capture(&(udf->definition), start_token, c_token - 1); #else while (!END_OF_COMMAND) c_token++; int_error(start_token, "This copy of gnuplot does not support plugins"); #endif } /* process invalid commands and, on OS/2, REXX commands */ void invalid_command() { int save_token = c_token; #ifdef OS2 if (token[c_token].is_token) { int rc; rc = ExecuteMacro(gp_input_line + token[c_token].start_index, token[c_token].length); if (rc == 0) { c_token = num_tokens = 0; return; } } #endif /* Skip the rest of the command; otherwise we're left pointing to */ /* the middle of a command we already know is not valid. */ while (!END_OF_COMMAND) c_token++; int_error(save_token, "invalid command"); } /* * Auxiliary routines */ /* used by changedir_command() */ static int changedir(char *path) { #if defined(MSDOS) /* first deal with drive letter */ if (isalpha((unsigned char)path[0]) && (path[1] == ':')) { int driveno = toupper((unsigned char)path[0]) - 'A'; /* 0=A, 1=B, ... */ # if defined(__WATCOMC__) (void) _chdrive(driveno + 1); # elif 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) LPWSTR pathw = UnicodeText(path, encoding); int ret = !SetCurrentDirectoryW(pathw); free(pathw); return ret; #elif defined(__EMX__) && defined(OS2) return _chdir2(path); #else return chdir(path); #endif /* MSDOS etc. */ } /* used by replot_command() */ void replotrequest() { /* 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) { char *rest_args = &gp_input_line[token[c_token].start_index]; size_t replot_len = strlen(replot_line); size_t rest_len = strlen(rest_args); /* preserve commands following 'replot ;' */ /* move rest of input line to the start * necessary because of realloc() in extend_input_line() */ memmove(gp_input_line,rest_args,rest_len+1); /* reallocs if necessary */ while (gp_input_line_len < replot_len+rest_len+1) extend_input_line(); /* move old rest args off begin of input line to * make space for replot_line */ memmove(gp_input_line+replot_len,gp_input_line,rest_len+1); /* copy previous plot command to start of input line */ memcpy(gp_input_line, replot_line, replot_len); } 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 */ size_t newlen = strlen(replot_line) + token[last_token].start_index + token[last_token].length - token[c_token].start_index + 3; m_capture(&replot_args, c_token, last_token); /* might be empty */ while (gp_input_line_len < newlen) extend_input_line(); strcpy(gp_input_line, replot_line); strcat(gp_input_line, ", "); strcat(gp_input_line, replot_args); free(replot_args); } plot_token = 0; /* whole line to be saved as replot line */ SET_REFRESH_OK(E_REFRESH_NOT_OK, 0); /* start of replot will destroy existing data */ screen_ok = FALSE; num_tokens = scanner(&gp_input_line, &gp_input_line_len); c_token = 1; /* Skip the "plot" token */ if (almost_equals(0,"test")) { c_token = 0; test_command(); } else if (almost_equals(0,"s$plot")) plot3drequest(); else plotrequest(); } /* Support for input, shell, and help for various systems */ #ifdef VMS # include # include # include # include # include 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"); /* VMS-only version of read_line */ static int read_line(const char *prompt, int start) { int more; char expand_prompt[40]; current_prompt = prompt; /* HBB NEW 20040727 */ prompt_desc.dsc$w_length = strlen(prompt); prompt_desc.dsc$a_pointer = (char *) prompt; strcpy(expand_prompt, "_"); strncat(expand_prompt, prompt, sizeof(expand_prompt) - 2); do { line_desc.dsc$w_length = MAX_LINE_LEN - start; line_desc.dsc$a_pointer = &gp_input_line[start]; switch (status[1] = smg$read_composed_line(&vms_vkid, &vms_ktid, &line_desc, &prompt_desc, &vms_len)) { case SMG$_EOF: done(EXIT_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; gp_input_line[start] = NUL; inline_num++; if (gp_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(gp_input_line); line_desc.dsc$a_pointer = gp_input_line; more = 0; } } while (more); return 0; } # ifdef NO_GIH void help_command() { 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(NO_CARET, "can't open GNUPLOT$HELP"); } # endif /* NO_GIH */ void do_shell() { screen_ok = FALSE; c_token++; if ((vaxc$errno = lib$spawn()) != SS$_NORMAL) { os_error(NO_CARET, "spawn error"); } } static void do_system(const char *cmd) { if (!cmd) return; /* gp_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(cmd); line_desc.dsc$a_pointer = (char *) cmd; if ((vaxc$errno = lib$spawn(&line_desc)) != SS$_NORMAL) os_error(NO_CARET, "spawn error"); (void) putc('\n', stderr); } #endif /* VMS */ #ifdef NO_GIH #ifdef _WIN32 void help_command() { HWND parent; c_token++; parent = GetDesktopWindow(); /* open help file if necessary */ help_window = HtmlHelp(parent, winhelpname, HH_GET_WIN_HANDLE, (DWORD_PTR)NULL); if (help_window == NULL) { help_window = HtmlHelp(parent, winhelpname, HH_DISPLAY_TOPIC, (DWORD_PTR)NULL); if (help_window == NULL) { fprintf(stderr, "Error: Could not open help file \"" TCHARFMT "\"\n", winhelpname); return; } } if (END_OF_COMMAND) { /* show table of contents */ HtmlHelp(parent, winhelpname, HH_DISPLAY_TOC, (DWORD_PTR)NULL); } else { /* lookup topic in index */ HH_AKLINK link; char buf[128]; #ifdef UNICODE WCHAR wbuf[128]; #endif int start = c_token; while (!(END_OF_COMMAND)) c_token++; capture(buf, start, c_token - 1, sizeof(buf)); link.cbStruct = sizeof(HH_AKLINK) ; link.fReserved = FALSE; #ifdef UNICODE MultiByteToWideChar(WinGetCodepage(encoding), 0, buf, sizeof(buf), wbuf, sizeof(wbuf) / sizeof(WCHAR)); link.pszKeywords = wbuf; #else link.pszKeywords = buf; #endif link.pszUrl = NULL; link.pszMsgText = NULL; link.pszMsgTitle = NULL; link.pszWindow = NULL; link.fIndexOnFail = TRUE; HtmlHelp(parent, winhelpname, HH_KEYWORD_LOOKUP, (DWORD_PTR)&link); } } #else /* !_WIN32 */ #ifndef VMS void help_command() { while (!(END_OF_COMMAND)) c_token++; fputs("This gnuplot was not built with inline help\n", stderr); } #endif /* VMS */ #endif /* _WIN32 */ #endif /* NO_GIH */ /* * help_command: (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, help_command 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 ([email protected]) 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. * Lars - The "int toplevel" argument is gone. I have converted it to a * static variable. * * FIXME - helpbuf is never free()'d */ #ifndef NO_GIH void help_command() { static char *helpbuf = NULL; static char *prompt = NULL; static int toplevel = 1; 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 */ TBOOLEAN 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 ((help_ptr = getenv("GNUHELP")) == (char *) NULL) # ifndef SHELFIND /* if can't find environment variable then just use HELPFILE */ # if defined(MSDOS) || defined(OS2) help_ptr = HelpFile; # else help_ptr = HELPFILE; # endif # 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 */ /* if called recursively, toplevel == 0; toplevel must == 1 if called * from command() to get the same behaviour as before when toplevel * supplied as function argument */ toplevel = 1; len = base = strlen(helpbuf); start = ++c_token; /* find the end of the help command */ while (!(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, 1); /* 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) { strcpy (prompt, "Subtopic of "); strncat (prompt, helpbuf, MAX_LINE_LEN - 16); strcat (prompt, ": "); } else strcpy(prompt, "Help topic: "); read_line(prompt, 0); num_tokens = scanner(&gp_input_line, &gp_input_line_len); c_token = 0; more_help = !(END_OF_COMMAND); if (more_help) { c_token--; toplevel = 0; /* base for next level is all of current helpbuf */ help_command(); } } 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: int_error(NO_CARET, "Impossible case in switch"); break; } helpbuf[base] = NUL; /* cut it off where we started */ } #endif /* !NO_GIH */ #ifndef VMS static void do_system(const char *cmd) { int ierr; /* (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,... */ if (!cmd) return; restrict_popen(); #if defined(_WIN32) && !defined(WGP_CONSOLE) /* Open a console so we can see the command's output */ WinOpenConsole(); #endif #if defined(_WIN32) && !defined(HAVE_BROKEN_WSYSTEM) { LPWSTR wcmd = UnicodeText(cmd, encoding); ierr = _wsystem(wcmd); free(wcmd); } #else ierr = system(cmd); #endif report_error(ierr); } /* is_history_command: Test if line starts with an (abbreviated) history command. Modified copy of almost_equals() (util.c). */ static TBOOLEAN is_history_command(const char *line) { int i; int start = 0; int length = 0; int after = 0; const char str[] = "hi$story"; /* skip leading whitespace */ while (isblank((unsigned char) line[start])) start++; /* find end of "token" */ while ((line[start + length] != NUL) && !isblank((unsigned char) line[start + length])) length++; for (i = 0; i < length + after; i++) { if (str[i] != line[start + i]) { if (str[i] != '$') return FALSE; else { after = 1; start--; /* back up token ptr */ } } } /* i now beyond end of token string */ return (after || str[i] == '$' || str[i] == NUL); } # ifdef USE_READLINE /* keep some compilers happy */ static char *rlgets(char *s, size_t n, const char *prompt); static char * rlgets(char *s, size_t n, const 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) { # if defined(READLINE) || defined(HAVE_LIBREADLINE) int found; /* Initialize readline history functions */ using_history(); /* search in the history for entries containing line. * They may have other tokens before and after line, hence * the check on strcmp below. */ if (!is_history_command(line)) { if (!history_full) { found = history_search(line, -1); if (found != -1 && !strcmp(current_history()->line,line)) { /* this line is already in the history, remove the earlier entry */ HIST_ENTRY *removed = remove_history(where_history()); /* according to history docs we are supposed to free the stuff */ if (removed) { free(removed->line); free(removed->data); free(removed); } } } add_history(line); } # elif defined(HAVE_LIBEDITLINE) if (!is_history_command(line)) { /* deleting history entries does not work, so suppress adjacent duplicates only */ int found = 0; using_history(); if (!history_full) found = history_search(line, -1); if (found <= 0) add_history(line); } # endif } } if (line) { /* s will be NUL-terminated here */ safe_strncpy(s, line + leftover, n); leftover += strlen(s); if (line[leftover] == NUL) leftover = -1; return s; } return NULL; } # endif /* USE_READLINE */ # if defined(MSDOS) || defined(_WIN32) void do_shell() { screen_ok = FALSE; c_token++; if (user_shell) { # if defined(_WIN32) if (WinExec(user_shell, SW_SHOWNORMAL) <= 32) # elif defined(__DJGPP__) if (system(user_shell) == -1) # else if (spawnl(P_WAIT, user_shell, NULL) == -1) # endif /* !(_WIN32 || __DJGPP__) */ os_error(NO_CARET, "unable to spawn shell"); } } # elif defined(OS2) void do_shell() { screen_ok = FALSE; c_token++; if (user_shell) { if (system(user_shell) == -1) os_error(NO_CARET, "system() failed"); } (void) putc('\n', stderr); } # else /* !OS2 */ /* plain old Unix */ #define EXEC "exec " void do_shell() { static char exec[100] = EXEC; screen_ok = FALSE; c_token++; if (user_shell) { if (system(safe_strncpy(&exec[sizeof(EXEC) - 1], user_shell, sizeof(exec) - sizeof(EXEC) - 1))) os_error(NO_CARET, "system() failed"); } (void) putc('\n', stderr); } # endif /* !MSDOS */ /* read from stdin, everything except VMS */ # ifndef USE_READLINE # if defined(MSDOS) && !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)) /* emulate a fgets like input function with DOS cgets */ char * cgets_emu(char *str, int len) { static char buffer[128] = ""; static int leftover = 0; if (buffer[leftover] == NUL) { buffer[0] = 126; cgets(buffer); 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 /* !USE_READLINE */ /* this function is called for non-interactive operation. Its usage is * like fgets(), but additionally it checks for ipc events from the * terminals waitforinput() (if USE_MOUSE, and terminal is * mouseable). This function will be used when reading from a pipe. * fgets() reads in at most one less than size characters from stream and * stores them into the buffer pointed to by s. * Reading stops after an EOF or a newline. If a newline is read, it is * stored into the buffer. A '\0' is stored after the last character in * the buffer. */ static char* fgets_ipc( char *dest, /* string to fill */ int len) /* size of it */ { #ifdef USE_MOUSE if (term && term->waitforinput) { /* This a mouseable terminal --- must expect input from it */ int c; /* char gotten from waitforinput() */ size_t i=0; /* position inside dest */ dest[0] = '\0'; for (i=0; i < len-1; i++) { c = term->waitforinput(0); if ('\n' == c) { dest[i] = '\n'; i++; break; } else if (EOF == c) { dest[i] = '\0'; return (char*) 0; } else { dest[i] = c; } } dest[i] = '\0'; return dest; } else #endif return fgets(dest, len, stdin); } /* get a line from stdin, and display a prompt if interactive */ static char* gp_get_string(char * buffer, size_t len, const char * prompt) { # ifdef USE_READLINE if (interactive) return rlgets(buffer, len, prompt); else return fgets_ipc(buffer, len); # else if (interactive) PUT_STRING(prompt); return GET_STRING(buffer, len); # endif } /* Non-VMS version */ static int read_line(const char *prompt, int start) { TBOOLEAN more = FALSE; int last = 0; current_prompt = prompt; /* Once we start to read a new line, the tokens pointing into the old */ /* line are no longer valid. We used to _not_ clear things here, but */ /* that lead to errors when a mouse-triggered replot request came in */ /* while a new line was being read. Bug 3602388 Feb 2013. */ if (start == 0) { c_token = num_tokens = 0; gp_input_line[0] = '\0'; } do { /* grab some input */ if (gp_get_string(gp_input_line + start, gp_input_line_len - start, ((more) ? ">" : prompt)) == (char *) NULL) { /* end-of-file */ if (interactive) (void) putc('\n', stderr); gp_input_line[start] = NUL; inline_num++; if (start > 0 && curly_brace_count == 0) /* don't quit yet - process what we have */ more = FALSE; else return (1); /* exit gnuplot */ } else { /* normal line input */ /* gp_input_line must be NUL-terminated for strlen not to pass the * the bounds of this array */ last = strlen(gp_input_line) - 1; if (last >= 0) { if (gp_input_line[last] == '\n') { /* remove any newline */ gp_input_line[last] = NUL; if (last > 0 && gp_input_line[last-1] == '\r') gp_input_line[--last] = NUL; /* Watch out that we don't backup beyond 0 (1-1-1) */ if (last > 0) --last; } else if (last + 2 >= gp_input_line_len) { extend_input_line(); /* read rest of line, don't print "> " */ start = last + 1; more = TRUE; continue; /* else fall through to continuation handling */ } /* if(grow buffer?) */ if (gp_input_line[last] == '\\') { /* line continuation */ start = last; more = TRUE; } else more = FALSE; } else more = FALSE; } } while (more); return (0); } #endif /* !VMS */ /* * Walk through the input line looking for string variables preceded by @. * Replace the characters @ with the contents of the string. * Anything inside quotes is not expanded. * Allow up to 3 levels of nested macros. */ void string_expand_macros() { if (expand_1level_macros() && expand_1level_macros() && expand_1level_macros() && expand_1level_macros()) int_error(NO_CARET, "Macros nested too deeply"); } #define COPY_CHAR do {gp_input_line[o++] = *c; \ after_backslash = FALSE; } while (0) int expand_1level_macros() { TBOOLEAN in_squote = FALSE; TBOOLEAN in_dquote = FALSE; TBOOLEAN after_backslash = FALSE; TBOOLEAN in_comment= FALSE; int len; int o = 0; int nfound = 0; char *c; char *temp_string; char temp_char; char *m; struct udvt_entry *udv; /* Most lines have no macros */ if (!strchr(gp_input_line,'@')) return(0); temp_string = gp_alloc(gp_input_line_len,"string variable"); len = strlen(gp_input_line); if (len >= gp_input_line_len) len = gp_input_line_len-1; strncpy(temp_string,gp_input_line,len); temp_string[len] = '\0'; for (c=temp_string; len && c && *c; c++, len--) { switch (*c) { case '@': /* The only tricky bit */ if (!in_squote && !in_dquote && !in_comment && isalpha((unsigned char)c[1])) { /* Isolate the udv key as a null-terminated substring */ m = ++c; while (isalnum((unsigned char )*c) || (*c=='_')) c++; temp_char = *c; *c = '\0'; /* Look up the key and restore the original following char */ udv = get_udv_by_name(m); if (udv && udv->udv_value.type == STRING) { nfound++; m = udv->udv_value.v.string_val; FPRINTF((stderr,"Replacing @%s with \"%s\"\n",udv->udv_name,m)); while (strlen(m) + o + len > gp_input_line_len) extend_input_line(); while (*m) gp_input_line[o++] = (*m++); } else { gp_input_line[o] = '\0'; int_warn( NO_CARET, "%s is not a string variable",m); } *c-- = temp_char; } else COPY_CHAR; break; case '"': if (!after_backslash) in_dquote = !in_dquote; COPY_CHAR; break; case '\'': in_squote = !in_squote; COPY_CHAR; break; case '\\': if (in_dquote) after_backslash = !after_backslash; gp_input_line[o++] = *c; break; case '#': if (!in_squote && !in_dquote) in_comment = TRUE; default : COPY_CHAR; break; } } gp_input_line[o] = '\0'; free(temp_string); if (nfound) FPRINTF((stderr, "After string substitution command line is:\n\t%s\n", gp_input_line)); return(nfound); } /* much more than what can be useful */ #define MAX_TOTAL_LINE_LEN (1024 * MAX_LINE_LEN) int do_system_func(const char *cmd, char **output) { #if defined(VMS) || defined(PIPES) int c; FILE *f; int result_allocated, result_pos; char* result; int ierr = 0; # if defined(VMS) int chan, one = 1; struct dsc$descriptor_s pgmdsc = {0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0}; static $DESCRIPTOR(lognamedsc, "PLOT$MAILBOX"); # endif /* VMS */ /* open stream */ # ifdef VMS pgmdsc.dsc$a_pointer = cmd; pgmdsc.dsc$w_length = strlen(cmd); if (!((vaxc$errno = sys$crembx(0, &chan, 0, 0, 0, 0, &lognamedsc)) & 1)) os_error(NO_CARET, "sys$crembx failed"); if (!((vaxc$errno = lib$spawn(&pgmdsc, 0, &lognamedsc, &one)) & 1)) os_error(NO_CARET, "lib$spawn failed"); if ((f = fopen("PLOT$MAILBOX", "r")) == NULL) os_error(NO_CARET, "mailbox open failed"); # else /* everyone else */ restrict_popen(); if ((f = popen(cmd, "r")) == NULL) os_error(NO_CARET, "popen failed"); # endif /* everyone else */ /* get output */ result_pos = 0; result_allocated = MAX_LINE_LEN; result = gp_alloc(MAX_LINE_LEN, "do_system_func"); result[0] = NUL; while (1) { if ((c = getc(f)) == EOF) break; /* result <- c */ result[result_pos++] = c; if ( result_pos == result_allocated ) { if ( result_pos >= MAX_TOTAL_LINE_LEN ) { result_pos--; int_warn(NO_CARET, "*very* long system call output has been truncated"); break; } else { result = gp_realloc(result, result_allocated + MAX_LINE_LEN, "extend in do_system_func"); result_allocated += MAX_LINE_LEN; } } } result[result_pos] = NUL; /* close stream */ ierr = pclose(f); ierr = report_error(ierr); result = gp_realloc(result, strlen(result)+1, "do_system_func"); *output = result; return ierr; #else /* VMS || PIPES */ int_warn(NO_CARET, "system() requires support for pipes"); *output = gp_strdup(""); return 0; #endif /* VMS || PIPES */ } static int report_error(int ierr) { int reported_error; /* FIXME: This does not seem to report all reasonable errors correctly */ if (ierr == -1 && errno != 0) reported_error = errno; else reported_error = WEXITSTATUS(ierr); fill_gpval_integer("GPVAL_SYSTEM_ERRNO", reported_error); if (reported_error == 127) fill_gpval_string("GPVAL_SYSTEM_ERRMSG", "command not found or shell failed"); else fill_gpval_string("GPVAL_SYSTEM_ERRMSG", strerror(reported_error)); return reported_error; }