mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
1268 lines
37 KiB
C
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 responsible 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
|
|
* advantage 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
|
|
* parsing 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 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 */
|