/* GNUPLOT - parse.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. ]*/ #include "parse.h" #include "alloc.h" #include "command.h" #include "datablock.h" #include "eval.h" #include "help.h" #include "util.h" /* Protection mechanism for trying to parse a string followed by a + or - sign. * Also suppresses an undefined variable message if an unrecognized token * is encountered during try_to_get_string(). */ TBOOLEAN string_result_only = FALSE; static int parse_recursion_level; /* Exported globals: the current 'dummy' variable names */ char c_dummy_var[MAX_NUM_VAR][MAX_ID_LEN+1]; char set_dummy_var[MAX_NUM_VAR][MAX_ID_LEN+1] = { "x", "y" }; int fit_dummy_var[MAX_NUM_VAR]; TBOOLEAN scanning_range_in_progress = FALSE; /* This is used by plot_option_using() */ int at_highest_column_used = -1; /* This is checked by df_readascii() */ TBOOLEAN parse_1st_row_as_headers = FALSE; /* This is used by df_open() and df_readascii() */ udvt_entry *df_array = NULL; /* Iteration structures used for bookkeeping */ t_iterator * plot_iterator = NULL; t_iterator * set_iterator = NULL; /* Internal prototypes: */ static void convert __PROTO((struct value *, int)); static void extend_at __PROTO((void)); static union argument *add_action __PROTO((enum operators sf_index)); static void parse_expression __PROTO((void)); static void accept_logical_OR_expression __PROTO((void)); static void accept_logical_AND_expression __PROTO((void)); static void accept_inclusive_OR_expression __PROTO((void)); static void accept_exclusive_OR_expression __PROTO((void)); static void accept_AND_expression __PROTO((void)); static void accept_equality_expression __PROTO((void)); static void accept_relational_expression __PROTO((void)); static void accept_bitshift_expression __PROTO((void)); static void accept_additive_expression __PROTO((void)); static void accept_multiplicative_expression __PROTO((void)); static void parse_primary_expression __PROTO((void)); static void parse_conditional_expression __PROTO((void)); static void parse_logical_OR_expression __PROTO((void)); static void parse_logical_AND_expression __PROTO((void)); static void parse_inclusive_OR_expression __PROTO((void)); static void parse_exclusive_OR_expression __PROTO((void)); static void parse_AND_expression __PROTO((void)); static void parse_equality_expression __PROTO((void)); static void parse_relational_expression __PROTO((void)); static void parse_bitshift_expression __PROTO((void)); static void parse_additive_expression __PROTO((void)); static void parse_multiplicative_expression __PROTO((void)); static void parse_unary_expression __PROTO((void)); static void parse_sum_expression __PROTO((void)); static int parse_assignment_expression __PROTO((void)); static int parse_array_assignment_expression __PROTO((void)); static int is_builtin_function __PROTO((int t_num)); static void set_up_columnheader_parsing __PROTO((struct at_entry *previous )); static TBOOLEAN no_iteration __PROTO((t_iterator *)); static void reevaluate_iteration_limits __PROTO((t_iterator *iter)); static void reset_iteration __PROTO((t_iterator *iter)); /* Internal variables: */ static struct at_type *at = NULL; static int at_size = 0; static void convert(struct value *val_ptr, int t_num) { *val_ptr = token[t_num].l_val; } int int_expression() { return (int)real_expression(); } double real_expression() { double result; struct value a; result = real(const_express(&a)); gpfree_string(&a); return result; } void parse_reset_after_error() { string_result_only = FALSE; parse_recursion_level = 0; } /* JW 20051126: * Wrapper around const_express() called by try_to_get_string(). * Disallows top level + and - operators. * This enables things like set xtics ('-\pi' -pi, '-\pi/2' -pi/2.) */ struct value * const_string_express(struct value *valptr) { string_result_only = TRUE; const_express(valptr); string_result_only = FALSE; return (valptr); } struct value * const_express(struct value *valptr) { int tkn = c_token; if (END_OF_COMMAND) int_error(c_token, "constant expression required"); /* div - no dummy variables in a constant expression */ dummy_func = NULL; evaluate_at(temp_at(), valptr); /* run it and send answer back */ if (undefined) { int_error(tkn, "undefined value"); } if (valptr->type == ARRAY) { /* Make sure no one tries to free it later */ valptr->type = NOTDEFINED; int_error(NO_CARET, "const_express: unsupported array operation"); } return (valptr); } /* Used by plot2d/plot3d/stats/fit: * Parse an expression that may return a filename string, a datablock name, * a constant, or a dummy function using dummy variables x, y, ... * If any dummy variables are present, set (*atptr) to point to an action table * corresponding to the parsed expression, and return NULL. * Otherwise evaluate the expression and return a string if there is one. * The return value "str" and "*atptr" both point to locally-managed memory, * which must not be freed by the caller! */ char* string_or_express(struct at_type **atptr) { int i; TBOOLEAN has_dummies; static char *array_placeholder = "@@"; static char* str = NULL; free(str); str = NULL; df_array = NULL; if (atptr) *atptr = NULL; if (END_OF_COMMAND) int_error(c_token, "expression expected"); /* parsing for datablocks */ if (equals(c_token,"$")) return parse_datablock_name(); if (isstring(c_token) && (str = try_to_get_string())) return str; /* If this is a bare array name for an existing array, store a pointer */ /* for df_open() to use. "@@" is a magic pseudo-filename passed to */ /* df_open() that tells it to use the stored pointer. */ if (type_udv(c_token) == ARRAY && !equals(c_token+1, "[")) { df_array = add_udv(c_token++); return array_placeholder; } /* parse expression */ temp_at(); /* check if any dummy variables are used */ has_dummies = FALSE; for (i = 0; i < at->a_count; i++) { enum operators op_index = at->actions[i].index; if ( op_index == PUSHD1 || op_index == PUSHD2 || op_index == PUSHD || op_index == SUM ) { has_dummies = TRUE; break; } } if (!has_dummies) { /* no dummy variables: evaluate expression */ struct value val; evaluate_at(at, &val); if (!undefined && val.type == STRING) { /* prevent empty string variable from treated as special file '' or "" */ if (*val.v.string_val == '\0') { free(val.v.string_val); str = strdup(" "); } else { str = val.v.string_val; } } } /* prepare return */ if (atptr) *atptr = at; return str; } /* build an action table and return its pointer, but keep a pointer in at * so that we can free it later if the caller hasn't taken over management * of this table. */ struct at_type * temp_at() { if (at != NULL) free_at(at); at = (struct at_type *) gp_alloc(sizeof(struct at_type), "action table"); memset(at, 0, sizeof(*at)); /* reset action table !!! */ at_size = MAX_AT_LEN; parse_recursion_level = 0; parse_expression(); return (at); } /* build an action table, put it in dynamic memory, and return its pointer */ struct at_type * perm_at() { struct at_type *at_ptr; size_t len; (void) temp_at(); len = sizeof(struct at_type) + (at->a_count - MAX_AT_LEN) * sizeof(struct at_entry); at_ptr = (struct at_type *) gp_realloc(at, len, "perm_at"); at = NULL; /* invalidate at pointer */ return (at_ptr); } /* Create an action table that describes a call to column("string"). */ /* This is used by plot_option_using() to handle 'plot ... using "string"' */ struct at_type * create_call_column_at(char *string) { struct at_type *at = gp_alloc(sizeof(int) + 2*sizeof(struct at_entry),""); at->a_count = 2; at->actions[0].index = PUSHC; at->actions[0].arg.j_arg = 3; /* FIXME - magic number! */ at->actions[0].arg.v_arg.type = STRING; at->actions[0].arg.v_arg.v.string_val = string; at->actions[1].index = COLUMN; at->actions[1].arg.j_arg = 0; return (at); } static void extend_at() { size_t newsize = sizeof(struct at_type) + at_size * sizeof(struct at_entry); at = gp_realloc(at, newsize, "extend_at"); at_size += MAX_AT_LEN; FPRINTF((stderr, "Extending at size to %d\n", at_size)); } /* Add function number to the current action table */ static union argument * add_action(enum operators sf_index) { if (at->a_count >= at_size) { extend_at(); } at->actions[at->a_count].index = sf_index; return (&(at->actions[at->a_count++].arg)); } /* For external calls to parse_expressions() * parse_recursion_level is expected to be 0 */ static void parse_expression() { /* full expressions */ if (parse_assignment_expression()) return; parse_recursion_level++; accept_logical_OR_expression(); parse_conditional_expression(); parse_recursion_level--; } static void accept_logical_OR_expression() { /* ? : expressions */ accept_logical_AND_expression(); parse_logical_OR_expression(); } static void accept_logical_AND_expression() { accept_inclusive_OR_expression(); parse_logical_AND_expression(); } static void accept_inclusive_OR_expression() { accept_exclusive_OR_expression(); parse_inclusive_OR_expression(); } static void accept_exclusive_OR_expression() { accept_AND_expression(); parse_exclusive_OR_expression(); } static void accept_AND_expression() { accept_equality_expression(); parse_AND_expression(); } static void accept_equality_expression() { accept_relational_expression(); parse_equality_expression(); } static void accept_relational_expression() { accept_bitshift_expression(); parse_relational_expression(); } static void accept_bitshift_expression() { accept_additive_expression(); parse_bitshift_expression(); } static void accept_additive_expression() { accept_multiplicative_expression(); parse_additive_expression(); } static void accept_multiplicative_expression() { parse_unary_expression(); /* - things */ parse_multiplicative_expression(); /* * / % */ } static int parse_assignment_expression() { /* Check for assignment operator Var = */ if (isletter(c_token) && equals(c_token + 1, "=")) { /* push the variable name */ union argument *foo = add_action(PUSHC); char *varname = NULL; m_capture(&varname,c_token,c_token); foo->v_arg.type = STRING; foo->v_arg.v.string_val = varname; /* push a dummy variable that would be the index if this were an array */ /* FIXME: It would be nice to hide this from "show at" */ foo = add_action(PUSHC); foo->v_arg.type = NOTDEFINED; /* push the expression whose value it will get */ c_token += 2; parse_expression(); /* push the actual assignment operation */ (void) add_action(ASSIGN); return 1; } /* Check for assignment to an array element Array[] = */ if (parse_array_assignment_expression()) return 1; return 0; } /* * If an array assignment is the first thing on a command line it is handled by * the separate routine array_assignment(). * Here we catch assignments that are embedded in an expression. * Examples: * print A[2] = foo * A[1] = A[2] = A[3] = 0 */ static int parse_array_assignment_expression() { /* Check for assignment to an array element Array[] = */ if (isletter(c_token) && equals(c_token+1, "[")) { char *varname = NULL; union argument *foo; int save_action, save_token; /* Quick check for the most common false positives */ /* i.e. other constructs that begin with "name[" */ /* FIXME: quicker than the full test below, but do we care? */ if (equals(c_token+3, "]") && !equals(c_token+4, "=")) return 0; if (equals(c_token+3, ":")) /* substring s[foo:baz] */ return 0; /* Is this really a known array name? */ if (type_udv(c_token) != ARRAY) return 0; /* Save state of the action table and the command line */ save_action = at->a_count; save_token = c_token; /* push the array name */ m_capture(&varname,c_token,c_token); foo = add_action(PUSHC); foo->v_arg.type = STRING; foo->v_arg.v.string_val = varname; /* push the index */ c_token += 2; parse_expression(); /* If this wasn't really an array element assignment, back out. */ /* NB: Depending on what we just parsed, this may leak memory. */ if (!equals(c_token, "]") || !equals(c_token+1, "=")) { c_token = save_token; at->a_count = save_action; free(varname); return 0; } /* Now we evaluate the expression whose value it will get */ c_token += 2; parse_expression(); /* push the actual assignment operation */ (void) add_action(ASSIGN); return 1; } return 0; } /* add action table entries for primary expressions, i.e. either a * parenthesized expression, a variable name, a numeric constant, a * function evaluation, a power operator or postfix '!' (factorial) * expression. * Sep 2016 cardinality expression |Array| */ static void parse_primary_expression() { if (equals(c_token, "(")) { c_token++; parse_expression(); /* Expressions may be separated by a comma */ while (equals(c_token,",")) { c_token++; (void) add_action(POP); parse_expression(); } if (!equals(c_token, ")")) int_error(c_token, "')' expected"); c_token++; } else if (equals(c_token, "$")) { struct value a; c_token++; if (!isanumber(c_token)) { if (equals(c_token+1, "[")) { struct udvt_entry *datablock_udv; c_token--; datablock_udv = get_udv_by_name(parse_datablock_name()); if (!datablock_udv) int_error(c_token-2,"No such datablock"); add_action(PUSH)->udv_arg = datablock_udv; } else int_error(c_token, "Column number or datablock line expected"); } else if (equals(c_token,"N")) { /* $N == pseudocolumn -3 means "last column" */ c_token++; Ginteger(&a, -3); at_highest_column_used = -3; add_action(DOLLARS)->v_arg = a; } else { convert(&a, c_token++); if (a.type != INTGR || a.v.int_val < 0) int_error(c_token, "Positive integer expected"); if (at_highest_column_used < a.v.int_val) at_highest_column_used = a.v.int_val; add_action(DOLLARS)->v_arg = a; } } else if (equals(c_token, "|")) { struct udvt_entry *udv; c_token++; if (equals(c_token,"$")) { udv = get_udv_by_name(parse_datablock_name()); if (!udv) int_error(c_token-1, "no such datablock"); } else { udv = add_udv(c_token++); if (udv->udv_value.type != ARRAY) int_error(c_token-1, "not an array"); } add_action(PUSH)->udv_arg = udv; if (!equals(c_token, "|")) int_error(c_token, "'|' expected"); c_token++; add_action(CARDINALITY); } else if (isanumber(c_token)) { union argument *foo = add_action(PUSHC); convert(&(foo->v_arg), c_token); c_token++; } else if (isletter(c_token)) { /* Found an identifier --- check whether its a function or a * variable by looking for the parentheses of a function * argument list */ if (equals(c_token + 1, "(")) { enum operators whichfunc = is_builtin_function(c_token); struct value num_params; num_params.type = INTGR; #if (1) /* DEPRECATED */ if (whichfunc && (strcmp(ft[whichfunc].f_name,"defined")==0)) { /* Deprecated syntax: if (defined(foo)) ... */ /* New syntax: if (exists("foo")) ... */ struct udvt_entry *udv = add_udv(c_token+2); union argument *foo = add_action(PUSHC); foo->v_arg.type = INTGR; if (udv->udv_value.type == NOTDEFINED) foo->v_arg.v.int_val = 0; else foo->v_arg.v.int_val = 1; c_token += 4; /* skip past "defined ( ) " */ return; } #endif if (whichfunc) { c_token += 2; /* skip fnc name and '(' */ parse_expression(); /* parse fnc argument */ num_params.v.int_val = 1; while (equals(c_token, ",")) { c_token++; num_params.v.int_val++; parse_expression(); } if (!equals(c_token, ")")) int_error(c_token, "')' expected"); c_token++; /* So far sprintf is the only built-in function */ /* with a variable number of arguments. */ if (!strcmp(ft[whichfunc].f_name,"sprintf")) add_action(PUSHC)->v_arg = num_params; /* v4 timecolumn only had 1 param; v5 has 2. Accept either */ if (!strcmp(ft[whichfunc].f_name,"timecolumn")) add_action(PUSHC)->v_arg = num_params; /* The column() function has side effects requiring special handling */ if (!strcmp(ft[whichfunc].f_name,"column")) { set_up_columnheader_parsing( &(at->actions[at->a_count-1]) ); } (void) add_action(whichfunc); } else { /* it's a call to a user-defined function */ enum operators call_type = (int) CALL; int tok = c_token; c_token += 2; /* skip func name and '(' */ parse_expression(); if (equals(c_token, ",")) { /* more than 1 argument? */ num_params.v.int_val = 1; while (equals(c_token, ",")) { num_params.v.int_val += 1; c_token += 1; parse_expression(); } add_action(PUSHC)->v_arg = num_params; call_type = (int) CALLN; } if (!equals(c_token, ")")) int_error(c_token, "')' expected"); c_token++; add_action(call_type)->udf_arg = add_udf(tok); } } else if (equals(c_token, "sum") && equals(c_token+1, "[")) { parse_sum_expression(); /* dummy_func==NULL is a flag to say no dummy variables active */ } else if (dummy_func) { if (equals(c_token, c_dummy_var[0])) { c_token++; add_action(PUSHD1)->udf_arg = dummy_func; fit_dummy_var[0]++; } else if (equals(c_token, c_dummy_var[1])) { c_token++; add_action(PUSHD2)->udf_arg = dummy_func; fit_dummy_var[1]++; } else { int i, param = 0; for (i = 2; i < MAX_NUM_VAR; i++) { if (equals(c_token, c_dummy_var[i])) { struct value num_params; num_params.type = INTGR; num_params.v.int_val = i; param = 1; c_token++; add_action(PUSHC)->v_arg = num_params; add_action(PUSHD)->udf_arg = dummy_func; fit_dummy_var[i]++; break; } } if (!param) { /* defined variable */ add_action(PUSH)->udv_arg = add_udv(c_token); c_token++; } } /* its a variable, with no dummies active - div */ } else { add_action(PUSH)->udv_arg = add_udv(c_token); c_token++; } } /* end if letter */ /* Maybe it's a string constant */ else if (isstring(c_token)) { union argument *foo = add_action(PUSHC); foo->v_arg.type = STRING; foo->v_arg.v.string_val = NULL; /* this dynamically allocated string will be freed by free_at() */ m_quote_capture(&(foo->v_arg.v.string_val), c_token, c_token); c_token++; } else { int_error(c_token, "invalid expression "); } /* The remaining operators are postfixes and can be stacked, e.g. */ /* Array[i]**2, so we may have to loop to catch all of them. */ while (TRUE) { /* add action code for ! (factorial) operator */ if (equals(c_token, "!")) { c_token++; (void) add_action(FACTORIAL); } /* add action code for ** operator */ else if (equals(c_token, "**")) { c_token++; parse_unary_expression(); (void) add_action(POWER); } /* Parse and add actions for range specifier applying to previous entity. * Currently the [beg:end] form is used to generate substrings, but could * also be used to extract vector slices. The [i] form is used to index * arrays, but could also be a shorthand for extracting a single-character * substring. */ else if (equals(c_token, "[") && !isanumber(c_token-1)) { /* handle '*' or empty start of range */ if (equals(++c_token,"*") || equals(c_token,":")) { union argument *empty = add_action(PUSHC); empty->v_arg.type = INTGR; empty->v_arg.v.int_val = 1; if (equals(c_token,"*")) c_token++; } else parse_expression(); /* handle array indexing (single value in square brackets) */ if (equals(c_token, "]")) { c_token++; (void) add_action(INDEX); continue; } if (!equals(c_token, ":")) int_error(c_token, "':' expected"); /* handle '*' or empty end of range */ if (equals(++c_token,"*") || equals(c_token,"]")) { union argument *empty = add_action(PUSHC); empty->v_arg.type = INTGR; empty->v_arg.v.int_val = 65535; /* should be INT_MAX */ if (equals(c_token,"*")) c_token++; } else parse_expression(); if (!equals(c_token, "]")) int_error(c_token, "']' expected"); c_token++; (void) add_action(RANGE); /* Whatever this is, it isn't another postfix operator */ } else { break; } } } /* HBB 20010309: Here and below: can't store pointers into the middle * of at->actions[]. That array may be realloc()ed by add_action() or * express() calls!. Access via index savepc1/savepc2, instead. */ static void parse_conditional_expression() { /* create action code for ? : expressions */ if (equals(c_token, "?")) { int savepc1, savepc2; /* Fake same recursion level for alternatives * set xlabel a>b ? 'foo' : 'bar' -1, 1 * FIXME: This won't work: * set xlabel a-b>c ? 'foo' : 'bar' offset -1, 1 */ parse_recursion_level--; c_token++; savepc1 = at->a_count; add_action(JTERN); parse_expression(); if (!equals(c_token, ":")) int_error(c_token, "expecting ':'"); c_token++; savepc2 = at->a_count; add_action(JUMP); at->actions[savepc1].arg.j_arg = at->a_count - savepc1; parse_expression(); at->actions[savepc2].arg.j_arg = at->a_count - savepc2; parse_recursion_level++; } } static void parse_logical_OR_expression() { /* create action codes for || operator */ while (equals(c_token, "||")) { int savepc; c_token++; savepc = at->a_count; add_action(JUMPNZ); /* short-circuit if already TRUE */ accept_logical_AND_expression(); /* offset for jump */ at->actions[savepc].arg.j_arg = at->a_count - savepc; (void) add_action(BOOLE); } } static void parse_logical_AND_expression() { /* create action code for && operator */ while (equals(c_token, "&&")) { int savepc; c_token++; savepc = at->a_count; add_action(JUMPZ); /* short-circuit if already FALSE */ accept_inclusive_OR_expression(); at->actions[savepc].arg.j_arg = at->a_count - savepc; /* offset for jump */ (void) add_action(BOOLE); } } static void parse_inclusive_OR_expression() { /* create action code for | operator */ while (equals(c_token, "|")) { c_token++; accept_exclusive_OR_expression(); (void) add_action(BOR); } } static void parse_exclusive_OR_expression() { /* create action code for ^ operator */ while (equals(c_token, "^")) { c_token++; accept_AND_expression(); (void) add_action(XOR); } } static void parse_AND_expression() { /* create action code for & operator */ while (equals(c_token, "&")) { c_token++; accept_equality_expression(); (void) add_action(BAND); } } static void parse_equality_expression() { /* create action codes for == and != numeric operators * eq and ne string operators */ while (TRUE) { if (equals(c_token, "==")) { c_token++; accept_relational_expression(); (void) add_action(EQ); } else if (equals(c_token, "!=")) { c_token++; accept_relational_expression(); (void) add_action(NE); } else if (equals(c_token, "eq")) { c_token++; accept_relational_expression(); (void) add_action(EQS); } else if (equals(c_token, "ne")) { c_token++; accept_relational_expression(); (void) add_action(NES); } else break; } } static void parse_relational_expression() { /* create action code for < > >= or <= * operators */ while (TRUE) { if (equals(c_token, ">")) { c_token++; accept_bitshift_expression(); (void) add_action(GT); } else if (equals(c_token, "<")) { /* Workaround for * in syntax of range constraints */ if (scanning_range_in_progress && equals(c_token+1, "*") ) { break; } c_token++; accept_bitshift_expression(); (void) add_action(LT); } else if (equals(c_token, ">=")) { c_token++; accept_bitshift_expression(); (void) add_action(GE); } else if (equals(c_token, "<=")) { c_token++; accept_bitshift_expression(); (void) add_action(LE); } else break; } } static void parse_bitshift_expression() { /* create action codes for << and >> operators */ while (TRUE) { if (equals(c_token, "<<")) { c_token++; accept_additive_expression(); (void) add_action(LEFTSHIFT); } else if (equals(c_token, ">>")) { c_token++; accept_additive_expression(); (void) add_action(RIGHTSHIFT); } else break; } } static void parse_additive_expression() { /* create action codes for +, - and . operators */ while (TRUE) { if (equals(c_token, ".")) { c_token++; accept_multiplicative_expression(); (void) add_action(CONCATENATE); /* If only string results are wanted * do not accept '-' or '+' at the top level. */ } else if (string_result_only && parse_recursion_level == 1) { break; } else if (equals(c_token, "+")) { c_token++; accept_multiplicative_expression(); (void) add_action(PLUS); } else if (equals(c_token, "-")) { c_token++; accept_multiplicative_expression(); (void) add_action(MINUS); } else break; } } static void parse_multiplicative_expression() { /* add action code for * / and % operators */ while (TRUE) { if (equals(c_token, "*")) { c_token++; parse_unary_expression(); (void) add_action(MULT); } else if (equals(c_token, "/")) { c_token++; parse_unary_expression(); (void) add_action(DIV); } else if (equals(c_token, "%")) { c_token++; parse_unary_expression(); (void) add_action(MOD); } else break; } } static void parse_unary_expression() { /* add code for unary operators */ if (equals(c_token, "!")) { c_token++; parse_unary_expression(); (void) add_action(LNOT); } else if (equals(c_token, "~")) { c_token++; parse_unary_expression(); (void) add_action(BNOT); } else if (equals(c_token, "-")) { struct at_entry *previous; c_token++; parse_unary_expression(); /* Collapse two operations PUSHC + UMINUS * into a single operation PUSHC */ previous = &(at->actions[at->a_count-1]); if (previous->index == PUSHC && previous->arg.v_arg.type == INTGR) { previous->arg.v_arg.v.int_val = -previous->arg.v_arg.v.int_val; } else if (previous->index == PUSHC && previous->arg.v_arg.type == CMPLX) { previous->arg.v_arg.v.cmplx_val.real = -previous->arg.v_arg.v.cmplx_val.real; previous->arg.v_arg.v.cmplx_val.imag = -previous->arg.v_arg.v.cmplx_val.imag; } else (void) add_action(UMINUS); } else if (equals(c_token, "+")) { /* unary + is no-op */ c_token++; parse_unary_expression(); } else parse_primary_expression(); } /* * Syntax: set link {x2|y2} {via inverse } * Create action code tables for the functions linking primary and secondary axes. * expression1 maps primary coordinates into the secondary coordinate space. * expression2 maps secondary coordinates into the primary coordinate space. */ void parse_link_via( struct udft_entry *udf ) { int start_token; /* Caller left us pointing at "via" or "inverse" */ c_token++; start_token = c_token; if (END_OF_COMMAND) int_error(c_token,"Missing expression"); /* Save action table for the linkage mapping */ dummy_func = udf; free_at(udf->at); udf->at = perm_at(); dummy_func = NULL; /* Save the mapping expression itself */ m_capture(&(udf->definition), start_token, c_token - 1); } /* create action code for 'sum' expressions */ static void parse_sum_expression() { /* sum [=] * - Pass a udf to f_sum (with action code (for ) that is not added * to the global action table). * - f_sum uses a newly created udv () to pass the current value of * to (resp. its ac). * - The original idea was to treat as function f(), but there * was the following problem: Consider 'g(x) = sum [k=1:4] f(k)'. There * are two dummy variables 'x' and 'k' from different functions 'g' and * 'f' which would require changing the parsing of dummy variables. */ char *errormsg = "Expecting 'sum [ = :] '\n"; char *varname = NULL; union argument *arg; struct udft_entry *udf; struct at_type * save_at; int save_at_size; int i; /* Caller already checked for string "sum [" so skip both tokens */ c_token += 2; /* */ if (!isletter(c_token)) int_error(c_token, errormsg); /* create a user defined variable and pass it to f_sum via PUSHC, since the * argument of f_sum is already used by the udf */ m_capture(&varname, c_token, c_token); add_udv(c_token); arg = add_action(PUSHC); Gstring(&(arg->v_arg), varname); c_token++; if (!equals(c_token, "=")) int_error(c_token, errormsg); c_token++; /* */ parse_expression(); if (!equals(c_token, ":")) int_error(c_token, errormsg); c_token++; /* */ parse_expression(); if (!equals(c_token, "]")) int_error(c_token, errormsg); c_token++; /* parse and convert it to a new action table. */ /* modeled on code from temp_at(). */ /* 1. save environment to restart parsing */ save_at = at; save_at_size = at_size; at = NULL; /* 2. save action table in a user defined function */ udf = (struct udft_entry *) gp_alloc(sizeof(struct udft_entry), "sum"); udf->next_udf = (struct udft_entry *) NULL; udf->udf_name = NULL; /* TODO maybe add a name and definition */ udf->at = perm_at(); udf->definition = NULL; udf->dummy_num = 0; for (i = 0; i < MAX_NUM_VAR; i++) (void) Ginteger(&(udf->dummy_values[i]), 0); /* 3. restore environment */ at = save_at; at_size = save_at_size; /* pass the udf to f_sum using the argument */ add_action(SUM)->udf_arg = udf; } /* find or add value and return pointer */ struct udvt_entry * add_udv(int t_num) { char varname[MAX_ID_LEN+1]; copy_str(varname, t_num, MAX_ID_LEN); if (token[t_num].length > MAX_ID_LEN-1) int_warn(t_num, "truncating variable name that is too long"); return add_udv_by_name(varname); } /* find or add function at index , and return pointer */ struct udft_entry * add_udf(int t_num) { struct udft_entry **udf_ptr = &first_udf; int i; while (*udf_ptr) { if (equals(t_num, (*udf_ptr)->udf_name)) return (*udf_ptr); udf_ptr = &((*udf_ptr)->next_udf); } /* get here => not found. udf_ptr points at first_udf or * next_udf field of last udf */ if (is_builtin_function(t_num)) int_warn(t_num, "Warning : udf shadowed by built-in function of the same name"); /* create and return a new udf slot */ *udf_ptr = (struct udft_entry *) gp_alloc(sizeof(struct udft_entry), "function"); (*udf_ptr)->next_udf = (struct udft_entry *) NULL; (*udf_ptr)->definition = NULL; (*udf_ptr)->at = NULL; (*udf_ptr)->udf_name = gp_alloc (token_len(t_num)+1, "user func"); copy_str((*udf_ptr)->udf_name, t_num, token_len(t_num)+1); for (i = 0; i < MAX_NUM_VAR; i++) (void) Ginteger(&((*udf_ptr)->dummy_values[i]), 0); return (*udf_ptr); } /* return standard function index or 0 */ static int is_builtin_function(int t_num) { int i; for (i = (int) SF_START; ft[i].f_name != NULL; i++) { if (equals(t_num, ft[i].f_name)) return (i); } return (0); } /* * Test for the existence of a function without triggering errors * Return values: * 0 no such function is defined * -1 built-in function * 1 user-defined function */ int is_function(int t_num) { struct udft_entry **udf_ptr = &first_udf; if (is_builtin_function(t_num)) return -1; while (*udf_ptr) { if (equals(t_num, (*udf_ptr)->udf_name)) return 1; udf_ptr = &((*udf_ptr)->next_udf); } return 0; } /* Look for iterate-over-plot constructs, of the form * for [ = : { : }] ... * If one (or more) is found, an iterator structure is allocated and filled * and a pointer to that structure is returned. * The pointer is NULL if no "for" statements are found. * If the iteration limits are constants, store them as is. * If they are given as expressions, store an action table for the expression. */ t_iterator * check_for_iteration() { char *errormsg = "Expecting iterator \tfor [ = : {: }]\n\t\t\tor\tfor [ in \"string of words\"]"; int nesting_depth = 0; t_iterator *iter = NULL; t_iterator *prev = NULL; t_iterator *this_iter = NULL; TBOOLEAN no_parent = FALSE; /* Now checking for iteration parameters */ /* Nested "for" statements are supported, each one corresponds to a node of the linked list */ while (equals(c_token, "for")) { struct udvt_entry *iteration_udv = NULL; t_value original_udv_value; char *iteration_string = NULL; int iteration_start; int iteration_end; int iteration_increment = 1; int iteration_current; int iteration = 0; struct at_type *iteration_start_at = NULL; struct at_type *iteration_end_at = NULL; c_token++; if (!equals(c_token++, "[") || !isletter(c_token)) int_error(c_token-1, errormsg); iteration_udv = add_udv(c_token++); original_udv_value = iteration_udv->udv_value; iteration_udv->udv_value.type = NOTDEFINED; if (equals(c_token, "=")) { c_token++; if (isanumber(c_token) && equals(c_token+1,":")) { /* Save the constant value only */ iteration_start = int_expression(); } else { /* Save the expression as well as the value */ struct value v; iteration_start_at = perm_at(); if (no_parent) { iteration_start = 0; } else { evaluate_at(iteration_start_at, &v); iteration_start = real(&v); } } if (!equals(c_token++, ":")) int_error(c_token-1, errormsg); if (equals(c_token,"*")) { iteration_end = INT_MAX; c_token++; } else if (isanumber(c_token) && (equals(c_token+1,":") || equals(c_token+1,"]"))) { /* Save the constant value only */ iteration_end = int_expression(); } else { /* Save the expression as well as the value */ struct value v; iteration_end_at = perm_at(); if (no_parent) { iteration_end = 0; } else { evaluate_at(iteration_end_at, &v); iteration_end = real(&v); } } if (equals(c_token,":")) { c_token++; iteration_increment = int_expression(); if (iteration_increment == 0) int_error(c_token-1, errormsg); } if (!equals(c_token++, "]")) int_error(c_token-1, errormsg); gpfree_array(&(iteration_udv->udv_value)); gpfree_string(&(iteration_udv->udv_value)); Ginteger(&(iteration_udv->udv_value), iteration_start); } else if (equals(c_token++, "in")) { /* Assume this is a string-valued expression. */ /* It might be worth treating a string constant as a special case */ struct value v; iteration_start_at = perm_at(); evaluate_at(iteration_start_at, &v); if (v.type != STRING) int_error(c_token-1, errormsg); if (!equals(c_token++, "]")) int_error(c_token-1, errormsg); iteration_string = v.v.string_val; iteration_start = 1; iteration_end = gp_words(iteration_string); gpfree_array(&(iteration_udv->udv_value)); gpfree_string(&(iteration_udv->udv_value)); Gstring(&(iteration_udv->udv_value), gp_word(iteration_string, 1)); } else /* Neither [i=B:E] or [s in "foo"] */ int_error(c_token-1, errormsg); iteration_current = iteration_start; this_iter = gp_alloc(sizeof(t_iterator), "iteration linked list"); this_iter->original_udv_value = original_udv_value; this_iter->iteration_udv = iteration_udv; this_iter->iteration_string = iteration_string; this_iter->iteration_start = iteration_start; this_iter->iteration_end = iteration_end; this_iter->iteration_increment = iteration_increment; this_iter->iteration_current = iteration_current; this_iter->iteration = iteration; this_iter->start_at = iteration_start_at; this_iter->end_at = iteration_end_at; this_iter->next = NULL; if (nesting_depth == 0) { /* first "for" statement: this will be the listhead */ iter = this_iter; } else { /* nested "for": attach newly created node to the end of the list */ prev->next = this_iter; } prev = this_iter; /* If some depth of a nested iteration evaluates to an empty range, the * evaluated limits of depths below it are moot (and possibly invalid). * This flag tells us to skip their evaluation to avoid irrelevant errors. */ if (no_iteration(this_iter)) { no_parent = TRUE; FPRINTF((stderr,"iteration at level %d is moot\n", nesting_depth)); } nesting_depth++; } return iter; } /* * Reevaluate the iteration limits * (in case they are functions whose parameters have taken * on a new value) */ static void reevaluate_iteration_limits(t_iterator *iter) { if (iter->start_at) { struct value v; evaluate_at(iter->start_at, &v); if (iter->iteration_string) { /* unnecessary if iteration string is a constant */ free(iter->iteration_string); if (v.type != STRING) int_error(NO_CARET, "corrupt iteration string"); iter->iteration_string = v.v.string_val; iter->iteration_start = 1; iter->iteration_end = gp_words(iter->iteration_string); } else { iter->iteration_start = real(&v); } } if (iter->end_at) { struct value v; evaluate_at(iter->end_at, &v); iter->iteration_end = real(&v); } } /* * Reset iteration at this level to start value. * Any iteration levels underneath are reset also. */ static void reset_iteration(t_iterator *iter) { if (!iter) return; reevaluate_iteration_limits(iter); iter->iteration = -1; iter->iteration_current = iter->iteration_start; if (iter->iteration_string) { gpfree_string(&(iter->iteration_udv->udv_value)); Gstring(&(iter->iteration_udv->udv_value), gp_word(iter->iteration_string, iter->iteration_current)); } else { /* This traps fatal user error of reassigning iteration variable to a string */ gpfree_string(&(iter->iteration_udv->udv_value)); Ginteger(&(iter->iteration_udv->udv_value), iter->iteration_current); } reset_iteration(iter->next); } /* * Increment the iteration position recursively. * returns TRUE if the iteration is still in range * returns FALSE if the incement put it past the end limit */ TBOOLEAN next_iteration(t_iterator *iter) { /* Once it goes out of range it will stay that way until reset */ if (!iter || no_iteration(iter)) return FALSE; /* Give sub-iterations a chance to advance */ if (next_iteration(iter->next)) { if (iter->iteration < 0) iter->iteration = 0; return TRUE; } /* Increment at this level */ if (iter->iteration < 0) { /* Just reset, haven't used start value yet */ iter->iteration = 0; if (!empty_iteration(iter)) return TRUE; } else { iter->iteration++; iter->iteration_current += iter->iteration_increment; } if (iter->iteration_string) { gpfree_string(&(iter->iteration_udv->udv_value)); Gstring(&(iter->iteration_udv->udv_value), gp_word(iter->iteration_string, iter->iteration_current)); } else { /* This traps fatal user error of reassigning iteration variable to a string */ gpfree_string(&(iter->iteration_udv->udv_value)); Ginteger(&(iter->iteration_udv->udv_value), iter->iteration_current); } /* If this runs off the end, leave the value out-of-range and return FALSE */ if (iter->iteration_increment > 0 && iter->iteration_end - iter->iteration_current < 0) return FALSE; if (iter->iteration_increment < 0 && iter->iteration_end - iter->iteration_current > 0) return FALSE; if (iter->next == NULL) return TRUE; /* Reset sub-iterations, if any */ reset_iteration(iter->next); /* Go back to top or call self recursively */ return next_iteration(iter); } /* * Returns TRUE if * - this really is an iteration and * - the top level iteration covers no usable range */ static TBOOLEAN no_iteration(t_iterator *iter) { if (!iter) return FALSE; if ((iter->iteration_end > iter->iteration_start && iter->iteration_increment < 0) || (iter->iteration_end < iter->iteration_start && iter->iteration_increment > 0)) { return TRUE; } return FALSE; } /* * Recursive test that no empty iteration exists in a nested set of iterations */ TBOOLEAN empty_iteration(t_iterator *iter) { if (!iter) return FALSE; else if (no_iteration(iter)) return TRUE; else return no_iteration(iter->next); } t_iterator * cleanup_iteration(t_iterator *iter) { while (iter) { t_iterator *next = iter->next; gpfree_string(&(iter->iteration_udv->udv_value)); iter->iteration_udv->udv_value = iter->original_udv_value; free(iter->iteration_string); free_at(iter->start_at); free_at(iter->end_at); free(iter); iter = next; } return NULL; } TBOOLEAN forever_iteration(t_iterator *iter) { if (!iter) return FALSE; else return (iter->iteration_end == INT_MAX); } /* The column() function requires special handling because * - It has side effects if reference to a column entry * requires matching it to the column header string. * - These side effects must be handled at the time the * expression is parsed rather than when it it evaluated. */ static void set_up_columnheader_parsing( struct at_entry *previous ) { /* column("string") means we expect the first row of */ /* a data file to contain headers rather than data. */ if (previous->index == PUSHC && previous->arg.v_arg.type == STRING) parse_1st_row_as_headers = TRUE; /* This allows plot ... using (column()) title columnhead */ if (previous->index == PUSHC && previous->arg.v_arg.type == INTGR) { if (at_highest_column_used < previous->arg.v_arg.v.int_val) at_highest_column_used = previous->arg.v_arg.v.int_val; } /* This attempts to catch plot ... using (column()) */ if (previous->index == PUSH) { udvt_entry *u = previous->arg.udv_arg; if (u->udv_value.type == INTGR) { if (at_highest_column_used < u->udv_value.v.int_val) at_highest_column_used = u->udv_value.v.int_val; } } /* NOTE: There is no way to handle ... using (column()) */ }