gnucash/libgnucash/app-utils/calculation/expression_parser.c
Geert Janssens 1238b9d8cd Prevent gcc from searching config.h in the current directory
This will avoid a ninja-build from picking up a config.h generated by the autotools build
(in the root build directory). Picking up the wrong config.h may lead to all kinds of
subtle issues if the autotools run was done with different options than the cmake run.
2017-10-26 14:05:17 +02:00

1268 lines
37 KiB
C

/***************************************************************************
expression-parser.c - description
-------------------
begin : Wednesday June 21 2000
email : tboldt@attglobal.net
Author : Terry D. Boldt
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
/*
* Functions to parse arthmetic expressions
* 6-21-2000
*/
/* Modified to support functions - Summer, 2002 -- jsled@asynchronous.org */
/* expression parser/evaluator use:
*
* Before describing the parser per se, I want to describe the
* structures used to contain the results returned from the
* parser. The structure is defined in "finvar.h":
*
* typedef struct var_store *var_store_ptr;
*
* typedef struct var_store {
* char *variable_name;
* char use_flag;
* char assign_flag;
* void *value;
* var_strore_ptr next_var;
* } var_store;
*
* The "use_flag" variable is for internal use of the parser and can
* be ignored by the user. The "variable_name" variable possibly
* points to a string containing the name of the value returned, a
* "variable name". If NULL, then this is a temporary value. The
* "value" variable points to a user defined structure containing the
* numeric value of the variable.
*
* As well, variables now have a VarStoreType, to distinguish between numeric
* and string values, as we want string arguments to functions.
*
* In designing and writing the parser, I decided early on that the
* parser should be an "expression parser/evaluator" and that the
* actual arithmetic was the responsibility of the caller/user.
*
* I decided that the parser should be totally independent of the
* numeric representation used, and thus the exact details of how the
* arithmetic was performed. To accomplish this, four functions are
* supplied by the user/caller:
*
* 1: trans_numeric - this function translates the text string into a
* numeric in the desired representation and returns a pointer to the
* representation as a (void *) this function has four parameters
* passed:
*
* 1: digit_str -- the actual text string of the
* numeric to be converted to the internal
* representation
*
* 2: radix_point -- the ASCII character used to
* represent the radix point
*
* 3: group character -- the ASCII character used
* to separate and group digits to the left of the
* radix
*
* 4: rstr -- a pointer to a location in which to
* return a pointer to the first character not
* part of the numeric string translated If this
* pointer is NULL, do not return a value. This
* parameter is the same as the second parameter
* of the standard C library functions "strtod" or
* "strtol"
*
* 2: numeric_ops - this function does the actual arithmetic on two
* numeric quantities in internal representation. It has three
* parameters passed:
*
* 1: op_sym -- the numeric operation to be
* performed. The possible values are defined
* in "finvar.h" and are:
*
* ADD_OP - addition
* SUB_OP - subtraction
* DIV_OP - division
* MUL_OP - multiplication
* ASN_OP - assignment
*
* 2: left_value - the left hand operand of the
* binary operator
*
* 3: right_value - the right hand operand of
* the binary operator Note: left_value and
* right_value are passed as (void *). This
* function is responsible for casting to the
* proper type to use. Note: this function should
* make no assumptions about overwriting or
* re-using either left_value or right_value,
* except for ASN_OP. Both values passed must be
* left unchanged by any operation except ASN_OP.
* This function is also responsible for
* allocating/freeing memory as necessary to
* perform the designated function and returning
* the result. I STRONGLY suggest that the result
* be returned in dynamically allocated memory. If
* static memory is used, the parser has no means
* of copying the returned result or managing
* static memory to prevent overwriting the result
* and invalidating the result.
*
* 3: negate_numeric - this function negates the value passed (as a (void *))
*
* 4: free_numeric - this function is responsible for freeing memory
* used by the internal numeric representation.
*
* 5: func_op - this function is repsonsible for handling function calls.
*
* I have included the file "numeric_ops.c" containing the above
* functions for the usual "double" and "int" representation of
* numerics. The functions perform integer or floating point
* operations as appropriate for the string entered by the user. The
* division operation is done in "double" since I do not think that
* anybody really wants (9 / 2) to equal 4 instead of 4.5 for
* financial operations. These functions use the structure defined in
* finvar.h:
*
* typedef struct numeric *numeric_ptr;
* typedef struct numeric {
* char type;
* union {
* long int int_value;
* double dbl_value;
* } value;
* } numeric;
*
* to contain all numeric values. The variable "type" in this
* structure can have the values:
*
* INT_TYPE
* DBL_TYPE
*
* which are defined in "finvar.h".
*
* All "named variables", variables defined by the user for storing
* intermediate results for future reference/use, and temporary
* variables used by the parser use the variable storage structure,
* var_store, defined above. The result of parsing and evaluating the
* string passed are returned in a variable storage structure
* specified by the caller.
*
* If the returned variable value is not named, i.e., "variable_name
* == NULL", then the user/caller is responsible for freeing the
* memory used by the internal representation of the numeric value.
* If, however, "variable_name != NULL", freeing the memory used by
* the internal numeric representation will cause a segmentation fault
* later, when the parser attempts to free the memory through a call
* to "free_numeric". In addition, freeing the memory will probably
* invalidate the numeric value contained therein and lead to
* pernicuous results when the value is used.
*
* If "variable_name != NULL", the user/caller should never attempt to
* free this memory, that is the sole responsibility of the parser.
*
* It may be that the calling function has certain "variables" that
* need to be "pre-defined" for the user to manipulate. In essence
* the function "pre-defining" variables sets up a linked list of
* variable storage structures with the proper "names" and numeric
* values. The number of "pre-defined" variables and a pointer to the
* structure array is passed to the parser in the initialization
* call. After the parser is eventually exited, the calling function
* is responsible for freeing any memory used by the "pre-defined"
* variables and their final numeric representation.
*
* There may also be strings in the expression, by quoting them in '"'
* characters. These are intended to be passed literally into functions; the
* result of using a string in a numeric operation is undefined. Presently,
* the expression-parser code does not check the variable types during
* parsing or evaluation.
*
* A second design goal of the parser was that it should be callable
* concurrently by multiple modules independently. That each module
* should be capable of using differing "pre-defined" variables and
* user defined variables and even internal numeric representations.
* To that end the calling module must first initialize the parser
* with a call to "init_parser". This call creates the parser
* internal structure for subsequent calls to the parser proper. The
* structure created and returned must then be passed to subsequent
* calls to the parser. When no further calls to the parser are to be
* made, the module then calls "exit_parser" with the pointer returned
* by "init_parser", so that the parser may release dynamically
* allocated memory.
*
* The parser recognizes the following binary operators:
*
* +
* -
* /
* *
* =
* +=
* -=
* /=
* *=
*
* In addition, the unary operators
*
* +
* -
*
* are recognized. All numerics are initially recognized as positive
* numbers. If negative, the unary '-' operator is applied. This saves
* the logic of having to recognize strings as
*
* -123
*
* The logic recognizes "-" and "123" separately. The '-' unary
* operator is then applied to negate the numeric. This also has the
* advanatge that the same logic can be used for
*
* -123
* +123.45
* +uvar
* -uvar
*
* In each case, the appropriate unary operator is applied to obtain
* the desired * result with no increase in the parsing logic. Thus
* keeping things as simple as possible.
*
* The parser also follows the C practice that the assignment
* operators return a value. Thus, allowing multiple assignments and
* assignment within expressions. The following expressions are all
* valid:
*
* nni = 123
* hnk = nni = 23.45
* jkl = 5 * (nj = 68.9)
*
* The first time variables are used in an expression, they are
* initialized to zero, 0. Thus, even if the following variables have
* not been assigned a value previously, the following expressions are
* valid:
*
* nni *= 123
* above results in zero in nni
* jk += 45.6
* above results in 45.6 in jk
* 56.8 - tyh
* result of above is 56.8
* tgh - 45.7
* above the same as
* -45.7
*
* After parsing the above expressions the variables nni, jk, tyh and
* tgh would all be defined.
*
* Functions are invoked with expressions of the format
*
* [_a-zA-Z]( <argument_0> : <argument_1> : ... : <argument_n> )
*
* where each argument can itself be a sub-expression [arithmetic operation
* or function call].
*
*
* There are six parser functions needed to use the parser/evaluator:
*
* Note: in the last five functions, in the function parameter (void
* *vp), "vp" is the pointer returned by the "init_parser" function.
*
* void *init_parser(var_store_ptr predefined_vars,
* gchar *radix_point,
* gchar *group_char,
* void *trans_numeric(char *digit_str,
* gchar *radix_point,
* gchar *group_char,
* char **rstr),
* void *numeric_ops(char op_sym,
* void *left_value,
* void *right_value),
* void *negate_numeric(void *value),
* void free_numeric(void *numeric_value),
* void *func_op(const char *fname, int argc, void **argv));
*
* This function is called by the module/function/whatever to
* initialize the parser. The parser returns a pointer to a
* structure that contains all relevant information for
* parsering strings. The pointer is returned as (void *)
* since all information is and should remain pertinent only
* to the parser. The calling function(s) should never rely on
* manipulating any information inside this structure
* directly, since it may and could change in the future. --
* The first parameter is a pointer to a the first element in
* a linked list of "pre-defined" variables the caller wishes
* to use with subsequent calls to the parser. -- The second
* parameter is the radix character to use in numeric strings
* in subsequent calls to the parser. -- the third parameter
* is the optional character used for grouping digits to the
* left of the radix. -- The fourth, fifth, sixth and seventh
* parameters are the functions I described above for the
* internal numeric representation desired by the calling
* function(s).
*
* void exit_parser(
* void *vp);
*
* This function is called to exit the parser and free all
* dynamically allocated memory used by the parser for an
* internal stack and user defined variables.
*
* unsigned get_parse_error(
* void *vp);
*
* If the parser is successful in complete parsing and
* evaluating the string passed to 'parse_string' below, that
* functions returns a NULL pointer. If, however, an error is
* encountered in parsing/evaluating the string, the
* 'parse_string' function returns a pointer to the character
* which caused the error. This call returns an unsigned
* integer designating the error encountered. The possible
* values are defined in the "finvar.h" file.
*
* var_store_ptr parser_get_vars(
* void *vp)
*
* This function returns a pointer to the first element of a
* linked list of variable storage structures containing the
* user defined named variables if any exist. NULL is
* returned if none exist. The calling function should not
* alter the variable names. The numeric values may be
* altered if the calling function author really knows what
* they are doing.
*
* unsigned delete_var(
* char *var_name,
* void *vp);
*
* This function will delete the user defined named variable
* with a name identical to the name string passed in the
* first parameter. If no user defined variable exists with an
* identical name, zero, 0, is returned. If the delete
* operation is successful, one, 1, is returned.
*
* char *parse_string(
* var_store_ptr value,
* char *string,
* void *vp);
*
* This function parses the string passed in the second
* parameter and returns a pointer to the last character not
* recognized upon a parsing error. If no error occurred, NULL
* is returned. The first parameter is a pointer to a variable
* storage structure to contain the result of the
* parser/evaluator.
*
* Note: The parser/evaluator uses a simple recursive descent
* parser. I decided on this type for the simple reason that for a
* simple four function calculator a recursive descent parser is, in
* my opinion, the easiest to construct. I also think that recursive
* descent parsers are easier for the human to understand and thus
* maintain.
*
* Also, the parser uses a stack which is dynamically allocated in
* memory and can grow as needed. I have not provided any mechanism
* for shrinking the stack. The initial stack size is set at 50
* slots. I really do not anticipate that under normal and even most
* extreme cases, that it will ever approach that size in actual
* use. Under "normal" operation, the stack will probably never exceed
* 3 or 4 slots in size and 50 slots is probably an overkill for
* normal use. However, since the stack is pointers and not entire
* structures, a stack size of 50 slots is not that much memory and
* can be tolerated by most users. Thus, a mechanism for shrinking the
* stack will probably never be needed.
*/
#include <config.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <glib.h>
#include "qof.h"
#define EXPRESSION_PARSER_STATICS
#include "finvar.h"
#define MAX_FUNC_ARG_LEN 255
/* structure to hold parser environment - environment particular to
* each caller */
typedef struct parser_env
{
unsigned stack_cnt;
unsigned stack_size;
var_store_ptr *stack;
var_store_ptr predefined_vars;
var_store_ptr named_vars;
var_store_ptr unnamed_vars;
const char *parse_str;
gchar *radix_point;
gchar *group_char;
char name[128];
char Token;
char asn_op;
char *tokens;
char *token_tail;
ParseError error_code;
void *numeric_value;
void *(*trans_numeric) (const char *digit_str,
gchar *radix_point, gchar *group_char, char **rstr);
void *(*numeric_ops) (char op_sym, void *left_value, void *right_value);
void *(*negate_numeric) (void *value);
void (*free_numeric) (void *numeric_value);
void *(*func_op)( const char *fname, int argc, void **argv );
}
parser_env;
#include "finproto.h"
#include "fin_static_proto.h"
#include "fin_spl_protos.h"
#define FN_TOKEN 'F'
#define ARG_TOKEN ':'
#define VAR_TOKEN 'V'
#define NUM_TOKEN 'I'
#define STR_TOKEN '"'
#define STACK_INIT 50
#define UNNAMED_VARS 100
#define NAMED_INCR 5
static char allowed_operators[] = "+-*/()=:";
parser_env_ptr
init_parser (var_store_ptr predefined_vars,
gchar *radix_point,
gchar *group_char,
void *trans_numeric (const char *digit_str,
gchar *radix_point,
gchar *group_char,
char **rstr),
void *numeric_ops (char op_sym,
void *left_value,
void *right_value),
void *negate_numeric (void *value),
void free_numeric (void *numeric_value),
void *func_op( const char *fname,
int argc, void **argv ))
{
parser_env_ptr pe = g_new0 (parser_env, 1);
pe->predefined_vars = predefined_vars;
pe->stack = g_new0 (var_store_ptr, STACK_INIT);
pe->stack_size = STACK_INIT;
pe->radix_point = radix_point;
pe->group_char = group_char;
pe->numeric_value = NULL;
pe->trans_numeric = trans_numeric;
pe->numeric_ops = numeric_ops;
pe->negate_numeric = negate_numeric;
pe->free_numeric = free_numeric;
pe->func_op = func_op;
return pe;
} /* init_parser */
void
exit_parser (parser_env_ptr pe)
{
var_store_ptr vars, bv;
if (pe == NULL)
return;
for (vars = pe->named_vars; vars; vars = bv)
{
g_free (vars->variable_name);
vars->variable_name = NULL;
if (vars->value)
pe->free_numeric (vars->value);
vars->value = NULL;
bv = vars->next_var;
g_free (vars);
} /* endfor */
pe->named_vars = NULL;
g_free (pe->stack);
pe->stack = NULL;
g_free (pe->tokens);
pe->tokens = NULL;
pe->token_tail = NULL;
if (pe->numeric_value)
pe->free_numeric (pe->numeric_value);
pe->numeric_value = NULL;
g_free (pe);
} /* exit_parser */
/* return parser error code */
ParseError get_parse_error (parser_env_ptr pe)
{
if (pe == NULL)
return PARSER_NO_ERROR;
return pe->error_code;
} /* get_parse_error */
/* return linked list of named variables which have been defined */
var_store_ptr parser_get_vars (parser_env_ptr pe)
{
if (pe == NULL)
return NULL;
return pe->named_vars;
} /* get_vars */
/* function to delete variable with specified name from named variables
* if it exists. If it exists return TRUE, 1, else return FALSE, 0 */
unsigned
delete_var (char *var_name, parser_env_ptr pe)
{
unsigned ret = FALSE;
var_store_ptr nv, tv;
if (pe == NULL)
return FALSE;
for (nv = pe->named_vars, tv = NULL; nv; tv = nv, nv = nv->next_var)
{
if (strcmp (nv->variable_name, var_name) == 0)
{
if (tv)
tv->next_var = nv->next_var;
else
pe->named_vars = nv->next_var;
g_free (nv->variable_name);
nv->variable_name = NULL;
pe->free_numeric (nv->value);
nv->value = NULL;
g_free (nv);
ret = TRUE;
break;
} /* endif */
} /* endfor */
return ret;
} /* delete_var */
/* parse string passed using parser environment passed return
* evaluated value in numeric structure passed, return NULL if no
* parse error. If parse error, return pointer to character at which
* error occurred. */
char *
parse_string (var_store_ptr value, const char *string, parser_env_ptr pe)
{
var_store_ptr retv;
var_store unnamed_vars[UNNAMED_VARS];
if (!pe || !string)
return NULL;
pe->unnamed_vars = unnamed_vars;
memset (unnamed_vars, 0, UNNAMED_VARS * sizeof (var_store));
pe->parse_str = string;
pe->error_code = PARSER_NO_ERROR;
g_free (pe->tokens);
pe->tokens = g_new0(char, strlen (string) + 1);
pe->token_tail = pe->tokens;
next_token (pe);
if (!pe->error_code)
assignment_op (pe);
if (!pe->error_code)
{
/* interpret (num) as -num */
if (strcmp (pe->tokens, "(I)") == 0)
{
var_store_ptr val;
val = pop (pe);
pe->negate_numeric (val->value);
push (val, pe);
}
}
if (pe->Token == EOS)
{
if ((pe->stack_cnt) && (retv = pop (pe)))
{
if (value != NULL)
*value = *retv;
pe->parse_str = NULL;
}
else
pe->error_code = STACK_UNDERFLOW;
}
pe->stack_cnt = 0;
pe->unnamed_vars = NULL;
return (char *) pe->parse_str;
} /* expression */
/* pop value off value stack */
static var_store_ptr
pop (parser_env_ptr pe)
{
var_store_ptr val;
if (pe->stack_cnt)
val = pe->stack[--(pe->stack_cnt)];
else
{
val = NULL;
pe->error_code = STACK_UNDERFLOW;
} /* endif */
return val;
} /* pop */
/* push value onto value stack */
static var_store_ptr
push (var_store_ptr push_value, parser_env_ptr pe)
{
if (pe->stack_cnt > pe->stack_size)
{
pe->stack_size += STACK_INIT;
pe->stack = g_realloc (pe->stack,
pe->stack_size * sizeof (var_store_ptr));
} /* endif */
pe->stack[(pe->stack_cnt)++] = push_value;
return push_value;
} /* push */
/* get/set variable with specified name - nothing fancy just scan each
* variable in linked list checking for a string match return variable
* found if match create new variable if none found */
static var_store_ptr
get_named_var (parser_env_ptr pe)
{
var_store_ptr retp = NULL, bv;
for (retp = pe->predefined_vars, bv = NULL; retp; retp = retp->next_var)
if (strcmp (retp->variable_name, pe->name) == 0)
break;
if (!retp && pe->named_vars)
for (retp = pe->named_vars; retp; bv = retp, retp = retp->next_var)
if (strcmp (retp->variable_name, pe->name) == 0)
break;
if (!retp)
{
retp = g_new0 (var_store, 1);
if (!pe->named_vars)
pe->named_vars = retp;
else
bv->next_var = retp;
retp->variable_name = g_strdup (pe->name);
retp->type = VST_NUMERIC;
retp->value =
pe->trans_numeric ("0", pe->radix_point, pe->group_char, NULL);
}
return retp;
} /* get_var */
/* get un-named temporary variable */
static var_store_ptr
get_unnamed_var (parser_env_ptr pe)
{
var_store_ptr retp = NULL;
unsigned cntr;
for (cntr = 0; cntr < UNNAMED_VARS; cntr++)
if (pe->unnamed_vars[cntr].use_flag == UNUSED_VAR)
{
retp = &(pe->unnamed_vars[cntr]);
retp->variable_name = NULL;
retp->use_flag = USED_VAR;
retp->type = VST_NUMERIC;
if (retp->value)
{
pe->free_numeric (retp->value);
retp->value = NULL;
} /* endif */
break;
} /* endif */
if (retp == NULL)
pe->error_code = PARSER_OUT_OF_MEMORY;
return retp;
} /* get_unnamed_var */
/* mark un-named temporary variable unused */
static void
free_var (var_store_ptr value, parser_env_ptr pe)
{
if (value == NULL)
return;
/* first check that not a named variable */
if (value->variable_name != NULL)
return;
value->use_flag = UNUSED_VAR;
if (value->value)
{
pe->free_numeric (value->value);
value->value = NULL;
}
} /* free_var */
static void
add_token (parser_env_ptr pe, char token)
{
pe->Token = token;
if ((token != EOS) || (*pe->token_tail != EOS))
{
*pe->token_tail = token;
pe->token_tail++;
}
}
/* parse next token from string */
static void
next_token (parser_env_ptr pe)
{
char *nstr;
const char *str_parse = pe->parse_str;
void *number;
while (isspace (*str_parse))
str_parse++;
pe->asn_op = EOS;
/* test for end of string */
if (!*str_parse)
{
add_token (pe, EOS);
}
/* test for possible operator */
else if (strchr (allowed_operators, *str_parse))
{
add_token (pe, *str_parse++);
if (*str_parse == ASN_OP)
{
/* BUG/FIXME: this seems to allow '(=' and ')=' [?], neither of which
* make sense. */
if (pe->Token != ASN_OP)
{
str_parse++;
pe->asn_op = pe->Token;
add_token (pe, ASN_OP);
}
else
pe->error_code = UNDEFINED_CHARACTER;
} /* endif */
}
/* test for string */
else if ( *str_parse == '"' )
{
nstr = pe->name;
/* skip over the '"'. */
str_parse++;
do
{
*nstr++ = *str_parse++;
}
while ( *str_parse != '"' );
*nstr = EOS;
str_parse++;
add_token( pe, STR_TOKEN );
}
/* test for name */
else if (isalpha (*str_parse)
|| (*str_parse == '_'))
{
int funcFlag = 0;
/* Check for variable or function */
/* If variable: add token. */
/* If function: parse args, build struct, add token. */
nstr = pe->name;
do
{
if ( *str_parse == '(' )
{
funcFlag = 1;
str_parse++;
break;
}
*nstr++ = *str_parse++;
}
while ((*str_parse == '_')
|| (*str_parse == '(')
|| isalpha (*str_parse)
|| isdigit (*str_parse));
*nstr = EOS;
if ( funcFlag )
{
add_token(pe, FN_TOKEN);
}
else
{
add_token(pe, VAR_TOKEN);
}
}
/* test for numeric token */
else if ((number = pe->trans_numeric (str_parse, pe->radix_point,
pe->group_char, &nstr)))
{
add_token (pe, NUM_TOKEN);
pe->numeric_value = number;
str_parse = nstr;
}
/* unrecognized character - error */
else
{
add_token (pe, *str_parse);
pe->error_code = UNDEFINED_CHARACTER;
} /* endif */
pe->parse_str = str_parse;
} /* next_token */
/* evaluate assignment operators,
* =
* +=
* -=
* \=
* *=
*/
/* FIXME: add non-numeric checking. */
static void
assignment_op (parser_env_ptr pe)
{
var_store_ptr vl; /* left value */
var_store_ptr vr; /* right value */
char ao;
add_sub_op (pe);
if (pe->error_code)
return;
while (pe->Token == ASN_OP)
{
vl = pop (pe);
if (pe->error_code)
return;
ao = pe->asn_op;
if (vl->variable_name)
{
next_token (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
assignment_op (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
vr = pop (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
vl->assign_flag = ASSIGNED_TO;
if (ao)
{
void *temp;
temp = vl->value;
vl->value = pe->numeric_ops (ao, vl->value, vr->value);
pe->free_numeric (temp);
}
else if (vl != vr)
{
if (!vr->variable_name)
{
pe->free_numeric (vl->value);
vl->value = vr->value;
vr->value = NULL;
}
else
{
pe->numeric_ops (ASN_OP, vl->value, vr->value);
}
free_var (vr, pe);
} /* endif */
push (vl, pe);
}
else
{
add_token (pe, EOS); /* error !!!!!!!!!! */
pe->error_code = NOT_A_VARIABLE;
free_var (vl, pe);
} /* endif */
} /* endwhile */
} /* assignment_op */
/* evaluate addition, subtraction operators */
/* FIXME: add non-numeric checking. */
static void
add_sub_op (parser_env_ptr pe)
{
var_store_ptr vl; /* left value */
var_store_ptr vr; /* right value */
var_store_ptr rslt; /* result */
char op;
multiply_divide_op (pe);
if (pe->error_code)
return;
while ((pe->Token == ADD_OP) || (pe->Token == SUB_OP))
{
op = pe->Token;
vl = pop (pe);
if (pe->error_code)
return;
next_token (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
multiply_divide_op (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
vr = pop (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
rslt = get_unnamed_var (pe);
if (pe->error_code)
{
free_var (vl, pe);
free_var (vr, pe);
return;
}
rslt->value = pe->numeric_ops (op, vl->value, vr->value);
free_var (vl, pe);
free_var (vr, pe);
push (rslt, pe);
} /* endwhile */
} /* add_sub_op */
/* evaluate multiplication, division operators */
/* FIXME: add non-numeric checking. */
static void
multiply_divide_op (parser_env_ptr pe)
{
var_store_ptr vl; /* left value */
var_store_ptr vr; /* right value */
var_store_ptr rslt; /* result */
char op;
primary_exp (pe);
if (pe->error_code)
return;
while ((pe->Token == MUL_OP) || (pe->Token == DIV_OP))
{
op = pe->Token;
vl = pop (pe);
if (pe->error_code)
return;
next_token (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
primary_exp (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
vr = pop (pe);
if (pe->error_code)
{
free_var (vl, pe);
return;
}
rslt = get_unnamed_var (pe);
if (pe->error_code)
{
free_var (vl, pe);
free_var (vr, pe);
return;
}
rslt->value = pe->numeric_ops (op, vl->value, vr->value);
free_var (vl, pe);
free_var (vr, pe);
push (rslt, pe);
} /* endwhile */
} /* multiply_divide_op */
/**
* Bug#334811, 308554: apply some basic grammar constraints.
* @return true if the expression is in error; pe->error_code will already
* contain the error.
**/
static int
check_expression_grammar_error(parser_env_ptr pe)
{
if (pe->Token == VAR_TOKEN
|| pe->Token == STR_TOKEN
|| pe->Token == NUM_TOKEN
|| pe->Token == FN_TOKEN)
{
add_token(pe, EOS);
pe->error_code = EXPRESSION_ERROR;
return TRUE;
}
return FALSE;
}
/* evaluate:
* unary '+' and '-'
* named variables
* numerics
* grouped expressions, "()"
* functions [ <name>( [exp : exp : ... : exp] ) ]
* strings
*/
static void
primary_exp (parser_env_ptr pe)
{
var_store_ptr rslt = NULL;
char *ident = NULL;
int funcArgCount;
char LToken = pe->Token;
/* If we are in a state where the non-stacked 'pe->name' is valuable, then
* save it before we process the next token. */
switch ( LToken )
{
case FN_TOKEN:
case STR_TOKEN:
ident = g_strdup( pe->name );
break;
}
next_token (pe);
if (pe->error_code)
return;
switch (LToken)
{
case '(':
assignment_op (pe);
if (pe->error_code)
return;
if (pe->Token == ')')
{
rslt = pop (pe);
if (pe->error_code)
return;
next_token (pe);
if (pe->error_code)
return;
}
else
{
add_token (pe, EOS); /* error here */
pe->error_code = UNBALANCED_PARENS;
} /* endif */
break;
case ADD_OP:
case SUB_OP:
primary_exp (pe);
if (pe->error_code)
return;
rslt = pop (pe);
if (pe->error_code)
return;
if (LToken == SUB_OP)
pe->negate_numeric (rslt->value);
break;
case NUM_TOKEN:
rslt = get_unnamed_var (pe);
if (pe->error_code)
return;
if (check_expression_grammar_error(pe))
return;
rslt->value = pe->numeric_value;
pe->numeric_value = NULL;
break;
case FN_TOKEN:
funcArgCount = 0;
if (pe->Token && pe->Token != ')')
{
do
{
assignment_op(pe);
if ( pe->error_code )
return;
funcArgCount++;
if (!pe->Token || pe->Token == ')')
{
break;
}
next_token(pe);
}
while (pe->Token != ARG_TOKEN);
}
if ( pe->Token != ')' )
{
add_token( pe, EOS );
pe->error_code = UNBALANCED_PARENS;
}
{
int i;
var_store_ptr val;
void **argv;
argv = g_new0( void*, funcArgCount );
for ( i = 0; i < funcArgCount; i++ )
{
/* fill, in back-to-front order, the funcArgCount tokens we just
* parsed out of the expression into a argument list to hand back
* to the caller's func_op callback. */
val = pop(pe);
argv[funcArgCount - i - 1] = val;
}
rslt = get_unnamed_var(pe);
rslt->value = (*pe->func_op)( ident, funcArgCount, argv );
for ( i = 0; i < funcArgCount; i++ )
{
free_var( argv[i], pe );
}
g_free( argv );
g_free( ident );
if ( rslt->value == NULL )
{
pe->error_code = NOT_A_FUNC;
add_token( pe, EOS );
return;
}
}
next_token(pe);
if (check_expression_grammar_error(pe))
return;
break;
case VAR_TOKEN:
if (check_expression_grammar_error(pe))
return;
rslt = get_named_var (pe);
break;
case STR_TOKEN:
if (!(pe->Token == ')'
|| pe->Token == ARG_TOKEN))
{
add_token(pe, EOS);
pe->error_code = EXPRESSION_ERROR;
return;
}
rslt = get_unnamed_var( pe );
rslt->type = VST_STRING;
rslt->value = ident;
break;
} /* endswitch */
if (rslt != NULL)
push (rslt, pe);
} /* primary_exp */