mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-16 14:28:40 +02:00
3316 lines
111 KiB
C
3316 lines
111 KiB
C
/***************************************************************/
|
|
/* */
|
|
/* EXPR.C */
|
|
/* */
|
|
/* Remind's expression-evaluation engine */
|
|
/* */
|
|
/* This engine breaks expression evaluation into two phases: */
|
|
/* 1) Compilation: The expression is parsed and a tree */
|
|
/* structure consisting of linked expr_node obbjects */
|
|
/* is created. */
|
|
/* 2) Evaluation: The expr_node tree is traversed and */
|
|
/* evaluated. */
|
|
/* */
|
|
/* This file is part of REMIND. */
|
|
/* Copyright (C) 1992-2025 by Dianne Skoll */
|
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/* */
|
|
/***************************************************************/
|
|
#include "config.h"
|
|
#include "err.h"
|
|
#include "types.h"
|
|
#include "protos.h"
|
|
#include "globals.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <unistd.h>
|
|
|
|
/*
|
|
The parser parses expressions into an internal tree
|
|
representation. Each node in the tree is an expr_node
|
|
object, which can be one of the following types:
|
|
0) N_FREE: An unallocated node
|
|
1) N_CONSTANT: A constant, such as 3, 13:30, '2024-01-01' or "foo"
|
|
2) N_LOCAL_VAR: A reference to a function argument.
|
|
3) N_VARIABLE: A reference to a global variable
|
|
4) N_SYSVAR: A reference to a system variable
|
|
5) N_BUILTIN_FUNC: A reference to a built-in function
|
|
6) N_USER_FUNC: A reference to a user-defined function
|
|
7) N_OPERATOR: A reference to an operator such as "+" or "&&"
|
|
8) N_SHORT_STR: A string constant short enough to store in u.name
|
|
9) N_ERROR: An uninitialized node, or a parse error
|
|
|
|
Additional types are N_SHORT_VAR, N_SHORT_SYSVAR, and N_SHORT_USER_FUNC
|
|
which behave identically to N_VARIABLE, N_SYSVAR and N_USER_FUNC
|
|
respectively, but have short-enough names to be stored more efficiently
|
|
in the expr_node object.
|
|
|
|
expr_nodes contain the following data, depending on their type:
|
|
|
|
1) N_CONSTANT: The constant value is stored in the node's u.value field
|
|
2) N_LOCAL_VAR: The offset into the function's argument list is stored in
|
|
u.arg
|
|
3) N_VARIABLE: The variable's name is stored in u.value.v.str
|
|
4) N_SYSVAR: The system variable's name is stored in u.value.v.str
|
|
5) N_BUILTIN_FUNC: A pointer to the function descriptor is stored in
|
|
u.builtin_func
|
|
6) N_USER_FUNC: The function's name is stored in u.value.v.str
|
|
7) N_OPERATOR: A pointer to the operator function is stored in
|
|
u.operator_func
|
|
8) N_SHORT_VAR: The variable's name is stored in u.name
|
|
9) N_SHORT_SYSVAR: The system variable's name is stored in u.name
|
|
10) N_SHORT_USER_FUNC: The function's name is stored in u.name
|
|
|
|
Nodes may have an arbitrary number of children, represented in the standard
|
|
two-pointer left-child, right-sibling representation.
|
|
|
|
As an example, the expression: 3 + 5 * min(x, 4) is represented like this:
|
|
|
|
+----------+
|
|
| OPERATOR |
|
|
| + |
|
|
+----------+
|
|
/ \
|
|
+----------+ +----------+
|
|
| CONSTANT | | OPERATOR |
|
|
| 3 | | * |
|
|
+----------+ +----------+
|
|
/ \
|
|
+----------+ +---------+
|
|
| CONSTANT | | BUILTIN |
|
|
| 5 | | min |
|
|
+----------+ +---------+
|
|
/ \
|
|
+----------+ +----------+
|
|
| VARIABLE | | CONSTANT |
|
|
| x | | 4 |
|
|
+----------+ +----------+
|
|
|
|
Evaluation is done recursively from the root of the tree. To
|
|
evaluate a node N:
|
|
|
|
1) Evaluate node N's children from left to right
|
|
2) Apply the operator or function to all of the resulting values
|
|
|
|
For nodes without children, the result of evaluation is:
|
|
1) For N_CONSTANT nodes: The constant
|
|
2) For N_VARIABLE nodes: The value of the variable
|
|
3) For N_SYSVAR nodes: The value of the system variable
|
|
4) For N_LOCAL_VAR nodes: The value of the user-defined function's argument
|
|
|
|
User-defined functions contain their own expr_node tree. This is
|
|
evaluated with the "locals" parameter set to the values of all
|
|
of the function's arguments.
|
|
|
|
Some operators don't evaluate all of their children. For example,
|
|
the || and && operators always evaluate their leftmost child. If
|
|
the result is true for ||, or false for &&, then the rightmost child
|
|
is not evaluated because its value is not needed to know the result
|
|
of the operator.
|
|
|
|
The built-in functions choose() and iif() also perform this sort of
|
|
short-circuit evaluation.
|
|
*/
|
|
|
|
/*
|
|
* The expression grammar is as follows:
|
|
*
|
|
* EXPR: OR_EXP |
|
|
* OR_EXP '||' EXPR
|
|
*
|
|
* OR_EXP: AND_EXP |
|
|
* AND_EXP '&&' OR_EXP
|
|
*
|
|
* AND_EXP: EQ_EXP |
|
|
* EQ_EXP '==' AND_EXP |
|
|
* EQ_EXP '!=' AND_EXP
|
|
*
|
|
* EQ_EXP: CMP_EXP |
|
|
* CMP_EXP '<' EQ_EXP |
|
|
* CMP_EXP '>' EQ_EXP |
|
|
* CMP_EXP '<=' EQ_EXP |
|
|
* CMP_EXP '<=' EQ_EXP
|
|
*
|
|
* CMP_EXP: TERM_EXP |
|
|
* TERM_EXP '+' CMP_EXP |
|
|
* TERM_EXP '-' CMP_EXP
|
|
*
|
|
* TERM_EXP: FACTOR_EXP |
|
|
* FACTOR_EXP '*' TERM_EXP |
|
|
* FACTOR_EXP '/' TERM_EXP |
|
|
* FACTOR_EXP '%' TERM_EXP
|
|
*
|
|
* FACTOR_EXP: '-' FACTOR_EXP |
|
|
* '!' FACTOR_EXP |
|
|
* ATOM
|
|
*
|
|
* ATOM: '+' ATOM |
|
|
* '(' EXPR ')' |
|
|
* CONSTANT |
|
|
* VAR |
|
|
* FUNCTION_CALL
|
|
*/
|
|
|
|
|
|
/* Constants for the "how" arg to compare() */
|
|
enum { EQ, GT, LT, GE, LE, NE };
|
|
|
|
/* Our pool of free expr_node objects, as a linked list, linked by child ptr */
|
|
static expr_node *expr_node_free_list = NULL;
|
|
|
|
#define TOKEN_IS(x) (!strcmp(DBufValue(&ExprBuf), x))
|
|
#define TOKEN_ISNOT(x) strcmp(DBufValue(&ExprBuf), x)
|
|
#define ISID(c) (isalnum(c) || (c) == '_')
|
|
|
|
/* Threshold above which to malloc space for function args rather
|
|
than using the stack */
|
|
#define STACK_ARGS_MAX 5
|
|
|
|
/* Maximum parse level before we bail (to avoid SEGV from filling stack)*/
|
|
#define MAX_PARSE_LEVEL 2000
|
|
|
|
static int parse_level_high_water = 0;
|
|
#define CHECK_PARSE_LEVEL() do { if (level > parse_level_high_water) { parse_level_high_water = level; if (level > MAX_PARSE_LEVEL) { *r = E_OP_STK_OVER; return NULL; } } } while(0)
|
|
|
|
/* Macro that only does "x" if the "-x" debug flag is on */
|
|
#define DBG(x) do { if (DebugFlag & DB_PRTEXPR) { x; } } while(0)
|
|
|
|
#define PEEK_TOKEN() peek_expr_token(&ExprBuf, *e)
|
|
#define GET_TOKEN() parse_expr_token(&ExprBuf, e)
|
|
|
|
/* The built-in function table lives in funcs.c */
|
|
extern BuiltinFunc Func[];
|
|
extern int NumFuncs;
|
|
|
|
/* Keep track of expr_node usage */
|
|
static int ExprNodesAllocated = 0;
|
|
static int ExprNodesHighWater = 0;
|
|
static int ExprNodesUsed = 0;
|
|
|
|
/* Forward references */
|
|
static expr_node * parse_expression_aux(char const **e, int *r, Var *locals, int level);
|
|
static char const *get_operator_name(expr_node *node);
|
|
static void print_expr_tree(expr_node *node, FILE *fp);
|
|
|
|
/* This is super-skanky... we keep track of the currently-executing
|
|
user-defined function in a global var */
|
|
static UserFunc *CurrentUserFunc = NULL;
|
|
|
|
/* How many expr_node objects to allocate at a time */
|
|
#define ALLOC_CHUNK 256
|
|
|
|
static char const *
|
|
find_end_of_expr(char const *s)
|
|
{
|
|
char const *e = s;
|
|
int in_quoted_string = 0;
|
|
int escaped = 0;
|
|
|
|
while(*e) {
|
|
if (in_quoted_string) {
|
|
if (escaped) {
|
|
escaped = 0;
|
|
e++;
|
|
continue;
|
|
}
|
|
if (*e == '\\') {
|
|
escaped = 1;
|
|
e++;
|
|
continue;
|
|
}
|
|
if (*e == '"') {
|
|
in_quoted_string = 0;
|
|
e++;
|
|
continue;
|
|
}
|
|
e++;
|
|
continue;
|
|
}
|
|
if (*e == '"') {
|
|
in_quoted_string = 1;
|
|
e++;
|
|
continue;
|
|
}
|
|
if (*e == ']') {
|
|
break;
|
|
}
|
|
e++;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* alloc_expr_node - allocate an expr_node object */
|
|
/* */
|
|
/* Allocates and returns an expr_node object. If all goes */
|
|
/* well, a pointer to the object is returned and *r is set */
|
|
/* to OK. On failure, *r is set to an error code and NULL */
|
|
/* is returned. */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *
|
|
alloc_expr_node(int *r)
|
|
{
|
|
expr_node *node;
|
|
if (!expr_node_free_list) {
|
|
expr_node_free_list = calloc(ALLOC_CHUNK, sizeof(expr_node));
|
|
if (!expr_node_free_list) {
|
|
*r = E_NO_MEM;
|
|
return NULL;
|
|
}
|
|
ExprNodesAllocated += ALLOC_CHUNK;
|
|
for (size_t i=0; i<ALLOC_CHUNK-1; i++) {
|
|
expr_node_free_list[i].child = &(expr_node_free_list[i+1]);
|
|
expr_node_free_list[i].sibling = NULL;
|
|
expr_node_free_list[i].type = N_FREE;
|
|
}
|
|
expr_node_free_list[ALLOC_CHUNK-1].child = NULL;
|
|
}
|
|
ExprNodesUsed++;
|
|
if (ExprNodesUsed > ExprNodesHighWater) ExprNodesHighWater = ExprNodesUsed;
|
|
node = expr_node_free_list;
|
|
expr_node_free_list = node->child;
|
|
node->type = N_ERROR;
|
|
node->child = NULL;
|
|
node->sibling = NULL;
|
|
node->num_kids = 0;
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* add_child - add a new child to an existing node */
|
|
/* */
|
|
/* adds "child" as the rightmost child of "parent". Always */
|
|
/* succeeds, so returns no value. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void
|
|
add_child(expr_node *parent, expr_node *child)
|
|
{
|
|
parent->num_kids++;
|
|
child->sibling = NULL;
|
|
if (!parent->child) {
|
|
parent->child = child;
|
|
return;
|
|
}
|
|
|
|
expr_node *cur = parent->child;
|
|
while (cur->sibling) {
|
|
cur = cur->sibling;
|
|
}
|
|
cur->sibling = child;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* debug_evaluation - print debugging information */
|
|
/* */
|
|
/* This is a helper function for the DB_PRTEXPR (-dx flag) */
|
|
/* */
|
|
/* It prints fmt and following printf-style args, followed */
|
|
/* by " => " and then either an error message for r != OK */
|
|
/* or a value that's the result of evaluation. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void
|
|
debug_evaluation(Value *ans, int r, char const *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
vfprintf(ErrFp, fmt, argptr);
|
|
fprintf(ErrFp, " => ");
|
|
if (r != OK) {
|
|
fprintf(ErrFp, "%s\n", GetErr(r));
|
|
} else {
|
|
PrintValue(ans, ErrFp);
|
|
fprintf(ErrFp, "\n");
|
|
}
|
|
va_end(argptr);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* debug_evaluation_binop - print debugging information */
|
|
/* */
|
|
/* This is a helper function for the DB_PRTEXPR (-dx flag) */
|
|
/* */
|
|
/* It's called specifically for binary operators. v1 and v2 */
|
|
/* are the operands (a NULL operand indicates one that was */
|
|
/* not evaluated), ans is the result, and r is the error code. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void
|
|
debug_evaluation_binop(Value *ans, int r, Value *v1, Value *v2, char const *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
if (v1) {
|
|
PrintValue(v1, ErrFp);
|
|
} else {
|
|
fprintf(ErrFp, "?");
|
|
}
|
|
fprintf(ErrFp, " ");
|
|
vfprintf(ErrFp, fmt, argptr);
|
|
fprintf(ErrFp, " ");
|
|
if (v2) {
|
|
PrintValue(v2, ErrFp);
|
|
} else {
|
|
fprintf(ErrFp, "?");
|
|
}
|
|
fprintf(ErrFp, " => ");
|
|
if (r != OK) {
|
|
fprintf(ErrFp, "%s\n", GetErr(r));
|
|
} else {
|
|
PrintValue(ans, ErrFp);
|
|
fprintf(ErrFp, "\n");
|
|
}
|
|
va_end(argptr);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* debug_evaluation_unop - print debugging information */
|
|
/* */
|
|
/* This is a helper function for the DB_PRTEXPR (-dx flag) */
|
|
/* */
|
|
/* It's called specifically for unary operators. v1 */
|
|
/* is the operand, ans is the result, and r is the error code */
|
|
/* */
|
|
/***************************************************************/
|
|
static void
|
|
debug_evaluation_unop(Value *ans, int r, Value *v1, char const *fmt, ...)
|
|
{
|
|
va_list argptr;
|
|
va_start(argptr, fmt);
|
|
vfprintf(ErrFp, fmt, argptr);
|
|
fprintf(ErrFp, " ");
|
|
if (v1) {
|
|
PrintValue(v1, ErrFp);
|
|
} else {
|
|
fprintf(ErrFp, "?");
|
|
}
|
|
fprintf(ErrFp, " => ");
|
|
if (r != OK) {
|
|
fprintf(ErrFp, "%s\n", GetErr(r));
|
|
} else {
|
|
PrintValue(ans, ErrFp);
|
|
fprintf(ErrFp, "\n");
|
|
}
|
|
va_end(argptr);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* get_var - get the value of a variable */
|
|
/* */
|
|
/* Gets the value of a global variable, with the name taken */
|
|
/* from the appropriate field of `node' */
|
|
/* */
|
|
/***************************************************************/
|
|
static int
|
|
get_var(expr_node *node, Value *ans, int *nonconst)
|
|
{
|
|
Var *v;
|
|
char const *str;
|
|
|
|
if (node->type == N_SHORT_VAR) {
|
|
str = node->u.name;
|
|
} else {
|
|
str = node->u.value.v.str;
|
|
}
|
|
v = FindVar(str, 0);
|
|
if (!v) {
|
|
Eprint("%s: `%s'", GetErr(E_NOSUCH_VAR), str);
|
|
return E_NOSUCH_VAR;
|
|
}
|
|
v->used_since_set = 1;
|
|
if (!v->is_constant) {
|
|
nonconst_debug(*nonconst, tr("Global variable `%s' makes expression non-constant"), str);
|
|
*nonconst = 1;
|
|
}
|
|
return CopyValue(ans, &(v->v));
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* get_sysvar - get the value of a system variable */
|
|
/* */
|
|
/* Gets the value of a system variable, with the name taken */
|
|
/* from the appropriate field of `node'. The name should not */
|
|
/* incude the leading '$' */
|
|
/* */
|
|
/***************************************************************/
|
|
static int
|
|
get_sysvar(expr_node const *node, Value *ans)
|
|
{
|
|
if (node->type == N_SHORT_SYSVAR) {
|
|
return GetSysVar(node->u.name, ans);
|
|
} else {
|
|
return GetSysVar(node->u.value.v.str, ans);
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* eval_builtin - evaluate a builtin function */
|
|
/* */
|
|
/* node is an expr_node of type N_BUILTIN_FUNC */
|
|
/* locals is an array of local variables (if any) or NULL */
|
|
/* The result of evaluation is stored in ans */
|
|
/* If a non-constant function is evaluated, *nonconst is set */
|
|
/* to 1. The return code is OK if all went well, or an error */
|
|
/* code otherwise. In case of an error, *ans is not updated */
|
|
/* */
|
|
/* The iif() and choose() functions know how to evaluate */
|
|
/* themselves when passed the expr_node. All other functions */
|
|
/* use an older API with a func_info object containing the */
|
|
/* evaluated arguments and a spot for the return value. */
|
|
/* */
|
|
/***************************************************************/
|
|
static int
|
|
eval_builtin(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
func_info info;
|
|
BuiltinFunc *f = node->u.builtin_func;
|
|
expr_node *kid;
|
|
int i, j, r;
|
|
Value stack_args[STACK_ARGS_MAX];
|
|
|
|
/* Check that we have the right number of argumens */
|
|
if (node->num_kids < f->minargs) {
|
|
Eprint("%s(): %s", f->name, GetErr(E_2FEW_ARGS));
|
|
return E_2FEW_ARGS;
|
|
}
|
|
if (node->num_kids > f->maxargs && f->maxargs != NO_MAX) {
|
|
Eprint("%s(): %s", f->name, GetErr(E_2MANY_ARGS));
|
|
return E_2MANY_ARGS;
|
|
}
|
|
|
|
if (FuncRecursionLevel >= MAX_RECURSION_LEVEL) {
|
|
return E_RECURSIVE;
|
|
}
|
|
|
|
/* If this is a new-style function that knows about expr_nodes,
|
|
let it evaluate itself */
|
|
if (f->newfunc) {
|
|
FuncRecursionLevel++;
|
|
r = node->u.builtin_func->newfunc(node, locals, ans, nonconst);
|
|
FuncRecursionLevel--;
|
|
return r;
|
|
}
|
|
|
|
/* It's an old-style function, so we need to simulate the
|
|
old function call API by building up a bundle of evaluated
|
|
arguments */
|
|
info.nargs = node->num_kids;
|
|
info.nonconst = 0;
|
|
|
|
if (info.nargs) {
|
|
if (info.nargs <= STACK_ARGS_MAX) {
|
|
info.args = stack_args;
|
|
} else {
|
|
info.args = malloc(info.nargs * sizeof(Value));
|
|
if (!info.args) {
|
|
return E_NO_MEM;
|
|
}
|
|
}
|
|
} else {
|
|
info.args = NULL;
|
|
}
|
|
|
|
kid = node->child;
|
|
i = 0;
|
|
|
|
/* Evaluate each child node and store it as the i'th argument */
|
|
while (kid) {
|
|
r = evaluate_expr_node(kid, locals, &(info.args[i]), nonconst);
|
|
if (r != OK) {
|
|
for (j=0; j<i; j++) {
|
|
DestroyValue(info.args[j]);
|
|
}
|
|
if (info.args != NULL && info.args != stack_args) {
|
|
free(info.args);
|
|
}
|
|
return r;
|
|
}
|
|
i++;
|
|
kid = kid->sibling;
|
|
}
|
|
|
|
/* Mark retval as uninitialized */
|
|
info.retval.type = ERR_TYPE;
|
|
|
|
/* Actually call the function */
|
|
if (DebugFlag & DB_PRTEXPR) {
|
|
fprintf(ErrFp, "%s(", f->name);
|
|
for (i=0; i<info.nargs; i++) {
|
|
if (i > 0) {
|
|
fprintf(ErrFp, " ");
|
|
}
|
|
PrintValue(&(info.args[i]), ErrFp);
|
|
if (i < info.nargs-1) {
|
|
fprintf(ErrFp, ",");
|
|
}
|
|
}
|
|
fprintf(ErrFp, ") => ");
|
|
}
|
|
FuncRecursionLevel++;
|
|
r = f->func(&info);
|
|
FuncRecursionLevel--;
|
|
|
|
/* Propagate non-constness */
|
|
if (info.nonconst) {
|
|
nonconst_debug(*nonconst, tr("Non-constant built-in function `%s' makes expression non-constant"), f->name);
|
|
*nonconst = 1;
|
|
}
|
|
if (r == OK) {
|
|
/* All went well; copy the result destructively */
|
|
(*ans) = info.retval;
|
|
|
|
/* Special case of const cunction */
|
|
if (!strcmp(f->name, "const")) {
|
|
if (*nonconst) {
|
|
nonconst_debug(0, tr("Non-constant expression converted to constant by `const' built-in function"));
|
|
}
|
|
*nonconst = 0;
|
|
}
|
|
/* Don't allow retval to be destroyed! */
|
|
info.retval.type = ERR_TYPE;
|
|
}
|
|
|
|
/* Debug */
|
|
if (DebugFlag & DB_PRTEXPR) {
|
|
if (r) {
|
|
fprintf(ErrFp, "%s", GetErr(r));
|
|
} else {
|
|
PrintValue(ans, ErrFp);
|
|
}
|
|
fprintf(ErrFp, "\n");
|
|
}
|
|
if (r != OK) {
|
|
Eprint("%s(): %s", f->name, GetErr(r));
|
|
}
|
|
/* Clean up */
|
|
if (info.args) {
|
|
for (i=0; i<info.nargs; i++) {
|
|
DestroyValue(info.args[i]);
|
|
}
|
|
if (info.args != NULL && info.args != stack_args) {
|
|
free(info.args);
|
|
}
|
|
}
|
|
DestroyValue(info.retval);
|
|
return r;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* debug_enter_userfunc - debugging helper function */
|
|
/* */
|
|
/* This function prints debugging info when a user-defined */
|
|
/* function is invoked. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void
|
|
debug_enter_userfunc(expr_node *node, Value *locals, int nargs)
|
|
{
|
|
char const *fname;
|
|
int i;
|
|
if (node->type == N_SHORT_USER_FUNC) {
|
|
fname = node->u.name;
|
|
} else {
|
|
fname = node->u.value.v.str;
|
|
}
|
|
fprintf(ErrFp, "%s %s(", GetErr(E_ENTER_FUN), fname);
|
|
for (i=0; i<nargs; i++) {
|
|
if (i) fprintf(ErrFp, ", ");
|
|
PrintValue(&(locals[i]), ErrFp);
|
|
}
|
|
fprintf(ErrFp, ")\n");
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* debug_exit_userfunc - debugging helper function */
|
|
/* */
|
|
/* This function prints debugging info when a user-defined */
|
|
/* function has been evaluated. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void
|
|
debug_exit_userfunc(expr_node *node, Value *ans, int r, Value *locals, int nargs)
|
|
{
|
|
char const *fname;
|
|
int i;
|
|
if (node->type == N_SHORT_USER_FUNC) {
|
|
fname = node->u.name;
|
|
} else {
|
|
fname = node->u.value.v.str;
|
|
}
|
|
fprintf(ErrFp, "%s %s(", GetErr(E_LEAVE_FUN), fname);
|
|
for (i=0; i<nargs; i++) {
|
|
if (i) fprintf(ErrFp, ", ");
|
|
PrintValue(&(locals[i]), ErrFp);
|
|
}
|
|
fprintf(ErrFp, ") => ");
|
|
if (r == OK) {
|
|
PrintValue(ans, ErrFp);
|
|
} else {
|
|
fprintf(ErrFp, "%s", GetErr(r));
|
|
}
|
|
fprintf(ErrFp, "\n");
|
|
}
|
|
|
|
static void
|
|
print_placeholders(int n)
|
|
{
|
|
int i;
|
|
for (i=0; i<n; i++) {
|
|
if (i > 0) {
|
|
fprintf(ErrFp, ", ");
|
|
}
|
|
fprintf(ErrFp, "?");
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* eval_userfunc - evaluate a user-defined function */
|
|
/* */
|
|
/* This function sets up a local value array by evaluating */
|
|
/* all of its children, and then evaluates the expr_node */
|
|
/* tree associated with the user-defined function. */
|
|
/* */
|
|
/***************************************************************/
|
|
static int
|
|
eval_userfunc(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
UserFunc *f;
|
|
UserFunc *previously_executing;
|
|
|
|
Value *new_locals = NULL;
|
|
expr_node *kid;
|
|
int i, r, j, pushed;
|
|
|
|
/* If we have <= STACK_ARGS_MAX, store them on the stack here */
|
|
Value stack_locals[STACK_ARGS_MAX];
|
|
|
|
/* Get the function name */
|
|
char const *fname;
|
|
if (node->type == N_SHORT_USER_FUNC) {
|
|
fname = node->u.name;
|
|
} else {
|
|
fname = node->u.value.v.str;
|
|
}
|
|
|
|
/* Find the function */
|
|
f = FindUserFunc(fname);
|
|
|
|
/* Bail if function does not exist */
|
|
if (!f) {
|
|
Eprint("%s: `%s'", GetErr(E_UNDEF_FUNC), fname);
|
|
return E_UNDEF_FUNC;
|
|
}
|
|
|
|
/* Make sure we have the right number of arguments */
|
|
if (node->num_kids < f->nargs) {
|
|
DBG(fprintf(ErrFp, "%s(", fname); print_placeholders(node->num_kids); fprintf(ErrFp, ") => %s\n", GetErr(E_2FEW_ARGS)));
|
|
Eprint("%s(): %s", f->name, GetErr(E_2FEW_ARGS));
|
|
return E_2FEW_ARGS;
|
|
}
|
|
if (node->num_kids > f->nargs) {
|
|
DBG(fprintf(ErrFp, "%s(", fname); print_placeholders(node->num_kids); fprintf(ErrFp, ") => %s\n", GetErr(E_2MANY_ARGS)));
|
|
Eprint("%s(): %s", f->name, GetErr(E_2MANY_ARGS));
|
|
return E_2MANY_ARGS;
|
|
}
|
|
|
|
/* Build up the array of locals */
|
|
if (node->num_kids) {
|
|
if (node->num_kids > STACK_ARGS_MAX) {
|
|
/* Too many args to fit on stack; put on heap */
|
|
new_locals = malloc(node->num_kids * sizeof(Value));
|
|
if (!new_locals) {
|
|
DBG(fprintf(ErrFp, "%s(...) => %s\n", fname, GetErr(E_NO_MEM)));
|
|
return E_NO_MEM;
|
|
}
|
|
} else {
|
|
new_locals = stack_locals;
|
|
}
|
|
|
|
/* Evaluate each child node and store in new_locals */
|
|
kid = node->child;
|
|
i = 0;
|
|
while(kid) {
|
|
r = evaluate_expr_node(kid, locals, &(new_locals[i]), nonconst);
|
|
if (r != OK) {
|
|
for (j=0; j<i; j++) {
|
|
DestroyValue(new_locals[j]);
|
|
}
|
|
if (new_locals != stack_locals) free(new_locals);
|
|
return r;
|
|
}
|
|
i++;
|
|
kid = kid->sibling;
|
|
}
|
|
}
|
|
|
|
/* Check for deep recursion */
|
|
if (FuncRecursionLevel >= MAX_RECURSION_LEVEL) {
|
|
for (j=0; j<node->num_kids; j++) {
|
|
DestroyValue(new_locals[j]);
|
|
}
|
|
if (new_locals != NULL && new_locals != stack_locals) free(new_locals);
|
|
return E_RECURSIVE;
|
|
}
|
|
|
|
/* Set currently-executing function (for debugging) */
|
|
previously_executing = CurrentUserFunc;
|
|
CurrentUserFunc = f;
|
|
|
|
FuncRecursionLevel++;
|
|
|
|
if (!f->is_constant) {
|
|
nonconst_debug(*nonconst, tr("User function `%s' defined in non-constant context makes expression non-constant"), f->name);
|
|
*nonconst = 1;
|
|
}
|
|
/* Add a call to the call stack for better error messages */
|
|
pushed = push_call(f->filename, f->name, f->lineno, f->lineno_start);
|
|
|
|
DBG(debug_enter_userfunc(node, new_locals, f->nargs));
|
|
|
|
/* Evaluate the function's expr_node tree */
|
|
r = evaluate_expr_node(f->node, new_locals, ans, nonconst);
|
|
|
|
DBG(debug_exit_userfunc(node, ans, r, new_locals, f->nargs));
|
|
|
|
if (r != OK) {
|
|
/* We print the error here in order to get the call stack trace */
|
|
Eprint("%s", GetErr(r));
|
|
}
|
|
|
|
if (pushed == OK) pop_call();
|
|
FuncRecursionLevel--;
|
|
CurrentUserFunc = previously_executing;
|
|
|
|
/* Clean up */
|
|
for (j=0; j<node->num_kids; j++) {
|
|
DestroyValue(new_locals[j]);
|
|
}
|
|
if (new_locals != stack_locals &&
|
|
new_locals != NULL) {
|
|
free(new_locals);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* evaluate_expression - evaluate an expression, possibly */
|
|
/* with timeout */
|
|
/* */
|
|
/* See evaluate_expr_node for a description of arguments and */
|
|
/* return value. */
|
|
/* */
|
|
/***************************************************************/
|
|
int
|
|
evaluate_expression(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
|
|
/* Set up time limits */
|
|
if (ExpressionEvaluationTimeLimit > 0) {
|
|
ExpressionTimeLimitExceeded = 0;
|
|
alarm(ExpressionEvaluationTimeLimit);
|
|
}
|
|
r = evaluate_expr_node(node, locals, ans, nonconst);
|
|
if (ExpressionEvaluationTimeLimit > 0) {
|
|
alarm(0);
|
|
ExpressionTimeLimitExceeded = 0;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static int CopyShortStr(Value *ans, expr_node const *node)
|
|
{
|
|
size_t len = strlen(node->u.name);
|
|
ans->v.str = malloc(len+1);
|
|
if (!ans->v.str) {
|
|
ans->type = ERR_TYPE;
|
|
return E_NO_MEM;
|
|
}
|
|
strcpy(ans->v.str, node->u.name);
|
|
ans->type = STR_TYPE;
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* evaluate_expr_node - evaluate an expression */
|
|
/* */
|
|
/* Arguments: */
|
|
/* node - the expr_node to evaluate */
|
|
/* locals - an array of arguments to a user-defined */
|
|
/* function. NULL if we're not in a function */
|
|
/* ans - pointer to a Value object that will hold */
|
|
/* the result of an evaluation */
|
|
/* nonconst - pointer to an integer that will be set to 1 */
|
|
/* if a non-constant object or function is */
|
|
/* evaluated */
|
|
/* Returns: */
|
|
/* OK if all goes well; a non-zero error code on failure. */
|
|
/* On failure, *ans is not updated. */
|
|
/* */
|
|
/***************************************************************/
|
|
int
|
|
evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
|
|
if (ExpressionEvaluationDisabled) {
|
|
return E_EXPR_DISABLED;
|
|
}
|
|
|
|
if (ExpressionTimeLimitExceeded) {
|
|
ExpressionTimeLimitExceeded = 0;
|
|
return E_TIME_EXCEEDED;
|
|
}
|
|
if (!node) {
|
|
return E_SWERR;
|
|
}
|
|
switch(node->type) {
|
|
case N_FREE:
|
|
case N_ERROR:
|
|
ans->type = ERR_TYPE;
|
|
return E_SWERR;
|
|
|
|
case N_SHORT_STR:
|
|
return CopyShortStr(ans, node);
|
|
|
|
case N_CONSTANT:
|
|
/* Constant node? Just return a copy of the constant */
|
|
return CopyValue(ans, &(node->u.value));
|
|
|
|
case N_SHORT_VAR:
|
|
r = get_var(node, ans, nonconst);
|
|
DBG(debug_evaluation(ans, r, "%s", node->u.name));
|
|
return r;
|
|
|
|
case N_VARIABLE:
|
|
r = get_var(node, ans, nonconst);
|
|
DBG(debug_evaluation(ans, r, "%s", node->u.value.v.str));
|
|
return r;
|
|
|
|
case N_LOCAL_VAR:
|
|
/* User-defined function argument? Copy the value */
|
|
r = CopyValue(ans, &(locals[node->u.arg]));
|
|
DBG(debug_evaluation(ans, r, "%s", CurrentUserFunc->args[node->u.arg]));
|
|
return r;
|
|
|
|
case N_SHORT_SYSVAR:
|
|
/* System var? Return it and note non-constant expression */
|
|
nonconst_debug(*nonconst, tr("System variable `$%s' makes expression non-constant"), node->u.name);
|
|
*nonconst = 1;
|
|
r = get_sysvar(node, ans);
|
|
DBG(debug_evaluation(ans, r, "$%s", node->u.name));
|
|
return r;
|
|
|
|
case N_SYSVAR:
|
|
/* System var? Return it and note non-constant expression */
|
|
nonconst_debug(*nonconst, tr("System variable `$%s' makes expression non-constant"), node->u.value.v.str);
|
|
*nonconst = 1;
|
|
r = get_sysvar(node, ans);
|
|
DBG(debug_evaluation(ans, r, "$%s", node->u.value.v.str));
|
|
return r;
|
|
|
|
case N_BUILTIN_FUNC:
|
|
/* Built-in function? Evaluate and note non-constant where applicable */
|
|
if (!node->u.builtin_func->is_constant) {
|
|
nonconst_debug(*nonconst, tr("Non-constant built-in function `%s' makes expression non-constant"), node->u.builtin_func->name);
|
|
*nonconst = 1;
|
|
}
|
|
return eval_builtin(node, locals, ans, nonconst);
|
|
|
|
case N_USER_FUNC:
|
|
case N_SHORT_USER_FUNC:
|
|
/* User-defined function? Evaluate it */
|
|
return eval_userfunc(node, locals, ans, nonconst);
|
|
|
|
case N_OPERATOR:
|
|
/* Operator? Evaluate it */
|
|
r = node->u.operator_func(node, locals, ans, nonconst);
|
|
if (r != OK) {
|
|
Eprint("`%s': %s", get_operator_name(node), GetErr(r));
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/* Can't happen */
|
|
return E_SWERR;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* how_to_op - convert a symbolic comparison constant to name */
|
|
/* */
|
|
/* Converts EQ, NE, etc to "==", "!=", etc for debugging */
|
|
/* */
|
|
/***************************************************************/
|
|
static char const *how_to_op(int how)
|
|
{
|
|
switch(how) {
|
|
case EQ: return "==";
|
|
case NE: return "!=";
|
|
case GE: return ">=";
|
|
case LE: return "<=";
|
|
case GT: return ">";
|
|
case LT: return "<";
|
|
default: return "???";
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare - evaluate a comparison operator. */
|
|
/* */
|
|
/* In addition to the usual arguments, "how" specifies */
|
|
/* specifically which comparison operator we're evaluating. */
|
|
/* */
|
|
/***************************************************************/
|
|
static int
|
|
compare(expr_node *node, Value *locals, Value *ans, int *nonconst, int how)
|
|
{
|
|
int r;
|
|
Value v1, v2;
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
r = evaluate_expr_node(node->child->sibling, locals, &v2, nonconst);
|
|
if (r != OK) {
|
|
DestroyValue(v1);
|
|
return r;
|
|
}
|
|
|
|
ans->type = INT_TYPE;
|
|
|
|
/* Different types? Only allowed for != and == */
|
|
if (v1.type != v2.type) {
|
|
if (how == EQ) {
|
|
ans->v.val = 0;
|
|
} else if (how == NE) {
|
|
ans->v.val = 1;
|
|
} else {
|
|
DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "%s", how_to_op(how)));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_BAD_TYPE;
|
|
}
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "%s", how_to_op(how)));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return OK;
|
|
}
|
|
|
|
/* Same types */
|
|
if (v1.type == STR_TYPE) {
|
|
switch(how) {
|
|
case EQ: ans->v.val = (strcmp(v1.v.str, v2.v.str) == 0); break;
|
|
case NE: ans->v.val = (strcmp(v1.v.str, v2.v.str) != 0); break;
|
|
case LT: ans->v.val = (strcmp(v1.v.str, v2.v.str) < 0); break;
|
|
case GT: ans->v.val = (strcmp(v1.v.str, v2.v.str) > 0); break;
|
|
case LE: ans->v.val = (strcmp(v1.v.str, v2.v.str) <= 0); break;
|
|
case GE: ans->v.val = (strcmp(v1.v.str, v2.v.str) >= 0); break;
|
|
}
|
|
} else {
|
|
switch(how) {
|
|
case EQ: ans->v.val = (v1.v.val == v2.v.val); break;
|
|
case NE: ans->v.val = (v1.v.val != v2.v.val); break;
|
|
case LT: ans->v.val = (v1.v.val < v2.v.val); break;
|
|
case GT: ans->v.val = (v1.v.val > v2.v.val); break;
|
|
case LE: ans->v.val = (v1.v.val <= v2.v.val); break;
|
|
case GE: ans->v.val = (v1.v.val >= v2.v.val); break;
|
|
}
|
|
}
|
|
DBG(debug_evaluation_binop(ans, r, &v1, &v2, "%s", how_to_op(how)));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare_eq - evaluate the "==" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int compare_eq(expr_node *node, Value *locals, Value *ans, int *nonconst) {
|
|
return compare(node, locals, ans, nonconst, EQ);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare_ne evaluate the "!=" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int compare_ne(expr_node *node, Value *locals, Value *ans, int *nonconst) {
|
|
return compare(node, locals, ans, nonconst, NE);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare_le - evaluate the "<=" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int compare_le(expr_node *node, Value *locals, Value *ans, int *nonconst) {
|
|
return compare(node, locals, ans, nonconst, LE);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare_ge - evaluate the ">=" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int compare_ge(expr_node *node, Value *locals, Value *ans, int *nonconst) {
|
|
return compare(node, locals, ans, nonconst, GE);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare_lt - evaluate the "<" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int compare_lt(expr_node *node, Value *locals, Value *ans, int *nonconst) {
|
|
return compare(node, locals, ans, nonconst, LT);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* compare_gt - evaluate the ">" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int compare_gt(expr_node *node, Value *locals, Value *ans, int *nonconst) {
|
|
return compare(node, locals, ans, nonconst, GT);
|
|
}
|
|
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* add - evaluate the "+" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int add(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
Value v1, v2;
|
|
size_t l1, l2;
|
|
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
r = evaluate_expr_node(node->child->sibling, locals, &v2, nonconst);
|
|
if (r != OK) {
|
|
DestroyValue(v1);
|
|
return r;
|
|
}
|
|
|
|
/* If both are ints, just add 'em */
|
|
if (v2.type == INT_TYPE && v1.type == INT_TYPE) {
|
|
/* Check for overflow */
|
|
if (_private_add_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_2HIGH, &v1, &v2, "+"));
|
|
return E_2HIGH;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val += v2.v.val;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "+"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a date plus an int, add 'em */
|
|
if ((v1.type == DATE_TYPE && v2.type == INT_TYPE) ||
|
|
(v1.type == INT_TYPE && v2.type == DATE_TYPE)) {
|
|
if (_private_add_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "+"));
|
|
return E_DATE_OVER;
|
|
}
|
|
|
|
*ans = v1;
|
|
ans->v.val += v2.v.val;
|
|
if (ans->v.val < 0) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "+"));
|
|
return E_DATE_OVER;
|
|
}
|
|
ans->type = DATE_TYPE;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "+"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a datetime plus an int or a time, add 'em */
|
|
if ((v1.type == DATETIME_TYPE && (v2.type == INT_TYPE || v2.type == TIME_TYPE)) ||
|
|
((v1.type == INT_TYPE || v1.type == TIME_TYPE) && v2.type == DATETIME_TYPE)) {
|
|
if (_private_add_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "+"));
|
|
return E_DATE_OVER;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val += v2.v.val;
|
|
if (ans->v.val < 0) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "+"));
|
|
return E_DATE_OVER;
|
|
}
|
|
ans->type = DATETIME_TYPE;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "+"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a time plus an int or a time plus a time,
|
|
add 'em mod MINUTES_PER_DAY */
|
|
if ((v1.type == TIME_TYPE && v2.type == INT_TYPE) ||
|
|
(v1.type == INT_TYPE && v2.type == TIME_TYPE) ||
|
|
(v1.type == TIME_TYPE && v2.type == TIME_TYPE)) {
|
|
if (_private_add_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "+"));
|
|
return E_DATE_OVER;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val += v2.v.val;
|
|
ans->v.val = ans->v.val % MINUTES_PER_DAY;
|
|
if (ans->v.val < 0) ans->v.val += MINUTES_PER_DAY;
|
|
ans->type = TIME_TYPE;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "+"));
|
|
return OK;
|
|
}
|
|
|
|
/* If either is a string, coerce them both to strings and concatenate */
|
|
if (v1.type == STR_TYPE || v2.type == STR_TYPE) {
|
|
/* Skanky... copy the values shallowly for debug */
|
|
Value o1 = v1;
|
|
Value o2 = v2;
|
|
if ( (r = DoCoerce(STR_TYPE, &v1)) ) {
|
|
DBG(debug_evaluation_binop(ans, r, &o1, &o2, "+"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return r;
|
|
}
|
|
if ( (r = DoCoerce(STR_TYPE, &v2)) ) {
|
|
DBG(debug_evaluation_binop(ans, r, &o1, &o2, "+"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return r;
|
|
}
|
|
l1 = strlen(v1.v.str);
|
|
l2 = strlen(v2.v.str);
|
|
if (MaxStringLen > 0 && (l1 + l2 > (size_t) MaxStringLen)) {
|
|
DBG(debug_evaluation_binop(ans, E_STRING_TOO_LONG, &o1, &o2, "+"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_STRING_TOO_LONG;
|
|
}
|
|
ans->v.str = malloc(l1 + l2 + 1);
|
|
if (!ans->v.str) {
|
|
DBG(debug_evaluation_binop(ans, E_NO_MEM, &o1, &o2, "+"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_NO_MEM;
|
|
}
|
|
ans->type = STR_TYPE;
|
|
strcpy(ans->v.str, v1.v.str);
|
|
strcpy(ans->v.str+l1, v2.v.str);
|
|
DBG(debug_evaluation_binop(ans, OK, &o1, &o2, "+"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return OK;
|
|
}
|
|
|
|
/* Don't handle other types yet */
|
|
DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "+"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_BAD_TYPE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* subtract - evaluate the binary "-" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int subtract(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
Value v1, v2;
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
r = evaluate_expr_node(node->child->sibling, locals, &v2, nonconst);
|
|
if (r != OK) {
|
|
DestroyValue(v1);
|
|
return r;
|
|
}
|
|
/* If they're both INTs, do subtraction */
|
|
if (v1.type == INT_TYPE && v2.type == INT_TYPE) {
|
|
if (_private_sub_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_2HIGH, &v1, &v2, "-"));
|
|
return E_2HIGH;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val -= v2.v.val;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "-"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a date minus an int, do subtraction, checking for underflow */
|
|
if (v1.type == DATE_TYPE && v2.type == INT_TYPE) {
|
|
if (_private_sub_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "-"));
|
|
return E_DATE_OVER;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val -= v2.v.val;
|
|
if (ans->v.val < 0) return E_DATE_OVER;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "-"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a datetime minus an int or a time, do subtraction,
|
|
* checking for underflow */
|
|
if (v1.type == DATETIME_TYPE && (v2.type == INT_TYPE || v2.type == TIME_TYPE)) {
|
|
if (_private_sub_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "-"));
|
|
return E_DATE_OVER;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val -= v2.v.val;
|
|
if (ans->v.val < 0) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "-"));
|
|
return E_DATE_OVER;
|
|
}
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "-"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a time minus an int, do subtraction mod MINUTES_PER_DAY */
|
|
if (v1.type == TIME_TYPE && v2.type == INT_TYPE) {
|
|
*ans = v1;
|
|
ans->v.val = (ans->v.val - v2.v.val) % MINUTES_PER_DAY;
|
|
if (ans->v.val < 0) ans->v.val += MINUTES_PER_DAY;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "-"));
|
|
return OK;
|
|
}
|
|
|
|
/* If it's a time minus a time or a date minus a date, do it */
|
|
if ((v1.type == TIME_TYPE && v2.type == TIME_TYPE) ||
|
|
(v1.type == DATETIME_TYPE && v2.type == DATETIME_TYPE) ||
|
|
(v1.type == DATE_TYPE && v2.type == DATE_TYPE)) {
|
|
if (_private_sub_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_DATE_OVER, &v1, &v2, "-"));
|
|
return E_DATE_OVER;
|
|
}
|
|
*ans = v1;
|
|
ans->v.val -= v2.v.val;
|
|
ans->type = INT_TYPE;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "-"));
|
|
return OK;
|
|
}
|
|
|
|
DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "-"));
|
|
/* Must be types illegal for subtraction */
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return E_BAD_TYPE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* multiply - evaluate the "*" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int multiply(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
Value v1, v2;
|
|
char *ptr;
|
|
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
r = evaluate_expr_node(node->child->sibling, locals, &v2, nonconst);
|
|
if (r != OK) {
|
|
DestroyValue(v1);
|
|
return r;
|
|
}
|
|
|
|
if (v1.type == INT_TYPE && v2.type == INT_TYPE) {
|
|
/* Prevent floating-point exception */
|
|
if ((v2.v.val == -1 && v1.v.val == INT_MIN) ||
|
|
(v1.v.val == -1 && v2.v.val == INT_MIN)) {
|
|
DBG(debug_evaluation_binop(ans, E_2HIGH, &v1, &v2, "*"));
|
|
return E_2HIGH;
|
|
}
|
|
if (_private_mul_overflow(v1.v.val, v2.v.val)) {
|
|
DBG(debug_evaluation_binop(ans, E_2HIGH, &v1, &v2, "*"));
|
|
return E_2HIGH;
|
|
}
|
|
*ans = v1;;
|
|
ans->v.val *= v2.v.val;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "*"));
|
|
return OK;
|
|
}
|
|
|
|
/* String times int means repeat the string that many times */
|
|
if ((v1.type == INT_TYPE && v2.type == STR_TYPE) ||
|
|
(v1.type == STR_TYPE && v2.type == INT_TYPE)) {
|
|
int rep = (v1.type == INT_TYPE ? v1.v.val : v2.v.val);
|
|
char const *str = (v1.type == INT_TYPE ? v2.v.str : v1.v.str);
|
|
int l;
|
|
|
|
/* Can't multiply by a negative number */
|
|
if (rep < 0) {
|
|
DBG(debug_evaluation_binop(ans, E_2LOW, &v1, &v2, "*"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_2LOW;
|
|
}
|
|
if (rep == 0 || !str || !*str) {
|
|
/* Empty string */
|
|
ans->type = STR_TYPE;
|
|
ans->v.str = malloc(1);
|
|
if (!ans->v.str) {
|
|
DBG(debug_evaluation_binop(ans, E_NO_MEM, &v1, &v2, "*"));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_NO_MEM;
|
|
}
|
|
*ans->v.str = 0;
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return OK;
|
|
}
|
|
|
|
/* Create the new value */
|
|
l = (int) strlen(str);
|
|
if (l * rep < 0) {
|
|
DBG(debug_evaluation_binop(ans, E_STRING_TOO_LONG, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return E_STRING_TOO_LONG;
|
|
}
|
|
if ((unsigned long) l * (unsigned long) rep >= (unsigned long) INT_MAX) {
|
|
DBG(debug_evaluation_binop(ans, E_STRING_TOO_LONG, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return E_STRING_TOO_LONG;
|
|
}
|
|
if (MaxStringLen > 0 && ((unsigned long) l * (unsigned long) rep) > (unsigned long)MaxStringLen) {
|
|
DBG(debug_evaluation_binop(ans, E_STRING_TOO_LONG, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return E_STRING_TOO_LONG;
|
|
}
|
|
ans->type = STR_TYPE;
|
|
ans->v.str = malloc(l * rep + 1);
|
|
if (!ans->v.str) {
|
|
DBG(debug_evaluation_binop(ans, E_NO_MEM, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return E_NO_MEM;
|
|
}
|
|
*ans->v.str = 0;
|
|
ptr = ans->v.str;
|
|
for (int i=0; i<rep; i++) {
|
|
strcat(ptr, str);
|
|
ptr += l;
|
|
}
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return OK;
|
|
}
|
|
DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "*"));
|
|
DestroyValue(v1); DestroyValue(v2);
|
|
return E_BAD_TYPE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* divide_or_mod - evaluate the "/" or "%" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int divide_or_mod(expr_node *node, Value *locals, Value *ans, int *nonconst, char op)
|
|
{
|
|
int r;
|
|
Value v1, v2;
|
|
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
r = evaluate_expr_node(node->child->sibling, locals, &v2, nonconst);
|
|
if (r != OK) {
|
|
DestroyValue(v1);
|
|
return r;
|
|
}
|
|
if (v1.type == INT_TYPE && v2.type == INT_TYPE) {
|
|
if (v2.v.val == 0) {
|
|
DBG(debug_evaluation_binop(ans, E_DIV_ZERO, &v1, &v2, "%c", op));
|
|
return E_DIV_ZERO;
|
|
}
|
|
/* This is the only way it can overflow */
|
|
if (v2.v.val == -1 && v1.v.val == INT_MIN) {
|
|
DBG(debug_evaluation_binop(ans, E_2HIGH, &v1, &v2, "%c", op));
|
|
return E_2HIGH;
|
|
}
|
|
*ans = v1;
|
|
if (op == '/') {
|
|
ans->v.val /= v2.v.val;
|
|
} else {
|
|
ans->v.val %= v2.v.val;
|
|
}
|
|
DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "%c", op));
|
|
return OK;
|
|
}
|
|
DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "%c", op));
|
|
DestroyValue(v1);
|
|
DestroyValue(v2);
|
|
return E_BAD_TYPE;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* do_mod - evaluate the "%" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int do_mod(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
return divide_or_mod(node, locals, ans, nonconst, '%');
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* divide - evaluate the "/" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int divide(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
return divide_or_mod(node, locals, ans, nonconst, '/');
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* logical_not - evaluate the unary "!" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int logical_not(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
Value v1;
|
|
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
ans->type = INT_TYPE;
|
|
ans->v.val = !truthy(&v1);
|
|
DBG(debug_evaluation_unop(ans, OK, &v1, "!"));
|
|
DestroyValue(v1);
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* unary_minus - evaluate the unary "-" operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int unary_minus(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
int r;
|
|
Value v1;
|
|
|
|
r = evaluate_expr_node(node->child, locals, &v1, nonconst);
|
|
if (r != OK) return r;
|
|
if (v1.type != INT_TYPE) {
|
|
DBG(debug_evaluation_unop(ans, E_BAD_TYPE, &v1, "-"));
|
|
DestroyValue(v1);
|
|
return E_BAD_TYPE;
|
|
}
|
|
if (v1.v.val == INT_MIN) {
|
|
DBG(debug_evaluation_unop(ans, E_2LOW, &v1, "-"));
|
|
DestroyValue(v1);
|
|
return E_2LOW;
|
|
}
|
|
ans->type = INT_TYPE;
|
|
ans->v.val = -(v1.v.val);
|
|
DBG(debug_evaluation_unop(ans, OK, &v1, "-"));
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* logical_binop - evaluate the short-circuit || or && ops */
|
|
/* */
|
|
/***************************************************************/
|
|
static int logical_binop(expr_node *node, Value *locals, Value *ans, int *nonconst, int is_and)
|
|
{
|
|
Value v;
|
|
char const *opname = (is_and) ? "&&" : "||";
|
|
|
|
/* Evaluate first arg */
|
|
int r = evaluate_expr_node(node->child, locals, &v, nonconst);
|
|
|
|
/* Bail on error */
|
|
if (r != OK) return r;
|
|
|
|
if (is_and) {
|
|
/* If first arg is false, return it */
|
|
if (!truthy(&v)) {
|
|
*ans = v;
|
|
DBG(debug_evaluation_binop(ans, OK, &v, NULL, opname));
|
|
return OK;
|
|
}
|
|
} else {
|
|
/* If first arg is true, return it */
|
|
if (truthy(&v)) {
|
|
*ans = v;
|
|
DBG(debug_evaluation_binop(ans, OK, &v, NULL, opname));
|
|
return OK;
|
|
}
|
|
}
|
|
|
|
/* Otherwise, evaluate and return second arg */
|
|
r = evaluate_expr_node(node->child->sibling, locals, ans, nonconst);
|
|
DBG(debug_evaluation_binop(ans, r, &v, ans, opname));
|
|
DestroyValue(v);
|
|
return r;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* logical_or - evaluate the short-circuit || operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int logical_or(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
return logical_binop(node, locals, ans, nonconst, 0);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* logical_and - evaluate the short-circuit && operator */
|
|
/* */
|
|
/***************************************************************/
|
|
static int logical_and(expr_node *node, Value *locals, Value *ans, int *nonconst)
|
|
{
|
|
return logical_binop(node, locals, ans, nonconst, 1);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_expr_token */
|
|
/* */
|
|
/* Read a token. */
|
|
/* */
|
|
/***************************************************************/
|
|
static int parse_expr_token(DynamicBuffer *buf, char const **in)
|
|
{
|
|
|
|
char c;
|
|
char c2;
|
|
char hexbuf[3];
|
|
|
|
DBufFree(buf);
|
|
|
|
/* Skip white space */
|
|
while (**in && isempty(**in)) (*in)++;
|
|
|
|
if (!**in) return OK;
|
|
|
|
c = *(*in)++;
|
|
if (DBufPutc(buf, c) != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
|
|
switch(c) {
|
|
case COMMA:
|
|
case END_OF_EXPR:
|
|
case '+':
|
|
case '-':
|
|
case '*':
|
|
case '/':
|
|
case '(':
|
|
case ')':
|
|
case '%': return OK;
|
|
|
|
case '&':
|
|
case '|':
|
|
case '=':
|
|
if (**in == c) {
|
|
if (DBufPutc(buf, c) != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
(*in)++;
|
|
} else {
|
|
Eprint("%s `%c' (%s `%c%c'?)", GetErr(E_PARSE_ERR), c, tr("did you mean"), c, c);
|
|
return E_PARSE_ERR;
|
|
}
|
|
return OK;
|
|
case '!':
|
|
case '>':
|
|
case '<':
|
|
if (**in == '=') {
|
|
if (DBufPutc(buf, '=') != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
(*in)++;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
|
|
/* Handle the parsing of quoted strings */
|
|
if (c == '\"') {
|
|
if (!**in) return E_MISS_QUOTE;
|
|
while (**in) {
|
|
/* Allow backslash-escapes */
|
|
if (**in == '\\') {
|
|
int r;
|
|
(*in)++;
|
|
if (!**in) {
|
|
DBufFree(buf);
|
|
return E_MISS_QUOTE;
|
|
}
|
|
switch(**in) {
|
|
case 'a':
|
|
r = DBufPutc(buf, '\a');
|
|
break;
|
|
case 'b':
|
|
r = DBufPutc(buf, '\b');
|
|
break;
|
|
case 'f':
|
|
r = DBufPutc(buf, '\f');
|
|
break;
|
|
case 'n':
|
|
r = DBufPutc(buf, '\n');
|
|
break;
|
|
case 'r':
|
|
r = DBufPutc(buf, '\r');
|
|
break;
|
|
case 't':
|
|
r = DBufPutc(buf, '\t');
|
|
break;
|
|
case 'v':
|
|
r = DBufPutc(buf, '\v');
|
|
break;
|
|
case 'x':
|
|
c2 = *(*in + 1);
|
|
if (!isxdigit(c2)) {
|
|
r = DBufPutc(buf, **in);
|
|
break;
|
|
}
|
|
hexbuf[0] = c2;
|
|
hexbuf[1] = 0;
|
|
(*in)++;
|
|
c2 = *(*in + 1);
|
|
if (isxdigit(c2)) {
|
|
hexbuf[1] = c2;
|
|
hexbuf[2] = 0;
|
|
(*in)++;
|
|
}
|
|
c2 = (int) strtol(hexbuf, NULL, 16);
|
|
if (!c2) {
|
|
Eprint(tr("\\x00 is not a valid escape sequence"));
|
|
r = E_PARSE_ERR;
|
|
} else {
|
|
r = DBufPutc(buf, c2);
|
|
}
|
|
break;
|
|
default:
|
|
r = DBufPutc(buf, **in);
|
|
break;
|
|
}
|
|
(*in)++;
|
|
if (r != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
continue;
|
|
}
|
|
c = *(*in)++;
|
|
if (DBufPutc(buf, c) != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
if (c == '\"') break;
|
|
}
|
|
if (c == '\"') return OK;
|
|
DBufFree(buf);
|
|
return E_MISS_QUOTE;
|
|
}
|
|
|
|
/* Dates can be specified with single-quotes */
|
|
if (c == '\'') {
|
|
if (!**in) return E_MISS_QUOTE;
|
|
while (**in) {
|
|
c = *(*in)++;
|
|
if (DBufPutc(buf, c) != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
if (c == '\'') break;
|
|
}
|
|
if (c == '\'') return OK;
|
|
DBufFree(buf);
|
|
return E_MISS_QUOTE;
|
|
}
|
|
|
|
if (!ISID(c) && c != '$') {
|
|
if (!c) {
|
|
Eprint("%s", GetErr(E_EOLN));
|
|
return E_EOLN;
|
|
}
|
|
Eprint("%s `%c'", GetErr(E_ILLEGAL_CHAR), c);
|
|
return E_ILLEGAL_CHAR;
|
|
}
|
|
|
|
if (c == '$' && **in && isalpha(**in)) {
|
|
while(ISID(**in)) {
|
|
if (DBufPutc(buf, **in) != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
(*in)++;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/* Parse a constant, variable name or function */
|
|
while (ISID(**in) || **in == ':' || **in == '.' || **in == TimeSep) {
|
|
if (DBufPutc(buf, **in) != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
(*in)++;
|
|
}
|
|
/* Chew up any remaining white space */
|
|
while (**in && isempty(**in)) (*in)++;
|
|
|
|
/* Peek ahead - is it an id followed by '('? Then we have a function call */
|
|
if (isalpha(*(DBufValue(buf))) ||
|
|
*DBufValue(buf) == '_') {
|
|
if (**in == '(') {
|
|
if (DBufPutc(buf, '(') != OK) {
|
|
DBufFree(buf);
|
|
return E_NO_MEM;
|
|
}
|
|
(*in)++;
|
|
}
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* peek_expr_token */
|
|
/* */
|
|
/* Read a token without advancing the input pointer */
|
|
/* */
|
|
/***************************************************************/
|
|
static int peek_expr_token(DynamicBuffer *buf, char const *in)
|
|
{
|
|
return parse_expr_token(buf, &in);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* free_expr_tree */
|
|
/* */
|
|
/* Recursively free the expr_node tree rooted at node */
|
|
/* Always returns NULL */
|
|
/* */
|
|
/***************************************************************/
|
|
expr_node * free_expr_tree(expr_node *node)
|
|
{
|
|
if (node && (node->type != N_FREE)) {
|
|
ExprNodesUsed--;
|
|
if (node->type == N_CONSTANT ||
|
|
node->type == N_VARIABLE ||
|
|
node->type == N_SYSVAR ||
|
|
node->type == N_USER_FUNC) {
|
|
DestroyValue(node->u.value);
|
|
}
|
|
free_expr_tree(node->child);
|
|
free_expr_tree(node->sibling);
|
|
node->child = (expr_node *) expr_node_free_list;
|
|
expr_node_free_list = (void *) node;
|
|
node->type = N_FREE;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* clone_expr_tree */
|
|
/* */
|
|
/* Clone an entire expr_tree. The clone shares no memory */
|
|
/* with the original. Returns NULL and sets *r on failure. */
|
|
/* */
|
|
/***************************************************************/
|
|
expr_node * clone_expr_tree(expr_node const *src, int *r)
|
|
{
|
|
int rc;
|
|
expr_node *dest = alloc_expr_node(r);
|
|
if (!dest) return NULL;
|
|
|
|
dest->type = src->type;
|
|
dest->num_kids = src->num_kids;
|
|
switch(dest->type) {
|
|
case N_FREE:
|
|
case N_ERROR:
|
|
*r = E_SWERR;
|
|
free_expr_tree(dest);
|
|
return NULL;
|
|
|
|
case N_CONSTANT:
|
|
case N_VARIABLE:
|
|
case N_SYSVAR:
|
|
case N_USER_FUNC:
|
|
rc = CopyValue(&(dest->u.value), &(src->u.value));
|
|
if (rc != OK) {
|
|
*r = rc;
|
|
free_expr_tree(dest);
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
case N_SHORT_STR:
|
|
case N_SHORT_VAR:
|
|
case N_SHORT_SYSVAR:
|
|
case N_SHORT_USER_FUNC:
|
|
strcpy(dest->u.name, src->u.name);
|
|
break;
|
|
|
|
case N_LOCAL_VAR:
|
|
dest->u.arg = src->u.arg;
|
|
break;
|
|
|
|
case N_BUILTIN_FUNC:
|
|
dest->u.builtin_func = src->u.builtin_func;
|
|
break;
|
|
|
|
case N_OPERATOR:
|
|
dest->u.operator_func = src->u.operator_func;
|
|
break;
|
|
}
|
|
|
|
if (src->child) {
|
|
dest->child = clone_expr_tree(src->child, r);
|
|
if (!dest->child) {
|
|
free_expr_tree(dest);
|
|
return NULL;
|
|
}
|
|
}
|
|
if (src->sibling) {
|
|
dest->sibling = clone_expr_tree(src->sibling, r);
|
|
if (!dest->sibling) {
|
|
free_expr_tree(dest);
|
|
return NULL;
|
|
}
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* set_long_name - set a long name in an expr_node */
|
|
/* */
|
|
/* Set the name field in an expr_node that's too long to fit */
|
|
/* in u.name -- instead, set in u.value.v.str */
|
|
/* */
|
|
/***************************************************************/
|
|
static int set_long_name(expr_node *node, char const *s)
|
|
{
|
|
char *buf;
|
|
size_t len = strlen(s);
|
|
if (len > VAR_NAME_LEN) len = VAR_NAME_LEN;
|
|
buf = malloc(len+1);
|
|
if (!buf) {
|
|
return E_NO_MEM;
|
|
}
|
|
StrnCpy(buf, s, VAR_NAME_LEN);
|
|
node->u.value.type = STR_TYPE;
|
|
node->u.value.v.str = buf;
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_function_call - parse a function call */
|
|
/* */
|
|
/* Starting from *e, parse a function call and return the */
|
|
/* parsed expr_node tree */
|
|
/* */
|
|
/* All parsing functions have the following arguments and */
|
|
/* return value: */
|
|
/* */
|
|
/* e - the current parse pointer. *e points to the */
|
|
/* character we're readin, and *e is updated as we parse */
|
|
/* */
|
|
/* r - holds the return code. Set to OK if all is well */
|
|
/* or a non-zero error code on error */
|
|
/* */
|
|
/* locals - an array of Vars representing named arguments */
|
|
/* for a user-defined function */
|
|
/* */
|
|
/* Returns an expr_node on success, NULL on failure. */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node * parse_function_call(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *arg;
|
|
char *s;
|
|
char const *ptr;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = alloc_expr_node(r);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
s = DBufValue(&ExprBuf);
|
|
*(s + DBufLen(&ExprBuf) - 1) = 0;
|
|
BuiltinFunc *f = FindBuiltinFunc(s);
|
|
if (f) {
|
|
node->u.builtin_func = f;
|
|
node->type = N_BUILTIN_FUNC;
|
|
} else {
|
|
if (strlen(s) < SHORT_NAME_BUF) {
|
|
node->type = N_SHORT_USER_FUNC;
|
|
strcpy(node->u.name, s);
|
|
strtolower(node->u.name);
|
|
} else {
|
|
if (set_long_name(node, s) != OK) {
|
|
*r = E_NO_MEM;
|
|
return free_expr_tree(node);
|
|
}
|
|
strtolower(node->u.value.v.str);
|
|
node->type = N_USER_FUNC;
|
|
}
|
|
}
|
|
/* Now parse the arguments */
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
free_expr_tree(node);
|
|
return NULL;
|
|
}
|
|
while(TOKEN_ISNOT(")")) {
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
if (TOKEN_IS(")")) {
|
|
continue;
|
|
}
|
|
arg = parse_expression_aux(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
free_expr_tree(node);
|
|
return NULL;
|
|
}
|
|
add_child(node, arg);
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
if (TOKEN_ISNOT(")") &&
|
|
TOKEN_ISNOT(",")) {
|
|
*r = E_EXPECT_COMMA;
|
|
return free_expr_tree(node);
|
|
}
|
|
if (TOKEN_IS(",")) {
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
if (TOKEN_IS(")")) {
|
|
Eprint("%s `)'", GetErr(E_PARSE_ERR));
|
|
*r = E_PARSE_ERR;
|
|
return free_expr_tree(node);
|
|
}
|
|
}
|
|
}
|
|
ptr = *e;
|
|
if (TOKEN_IS(")")) {
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
}
|
|
/* Check args for builtin funcs */
|
|
if (node->type == N_BUILTIN_FUNC) {
|
|
f = node->u.builtin_func;
|
|
if (node->num_kids < f->minargs) {
|
|
*e = ptr;
|
|
*r = E_2FEW_ARGS;
|
|
}
|
|
if (node->num_kids > f->maxargs && f->maxargs != NO_MAX) {
|
|
*e = ptr;
|
|
*r = E_2MANY_ARGS;
|
|
}
|
|
}
|
|
if (*r != OK) {
|
|
if (node->type == N_BUILTIN_FUNC) {
|
|
f = node->u.builtin_func;
|
|
Eprint("%s: %s", f->name, GetErr(*r));
|
|
}
|
|
return free_expr_tree(node);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* set_constant_value - Given a constant value token in */
|
|
/* ExprBuf, parse ExprBuf and set atom->u.value appropriately */
|
|
/* */
|
|
/***************************************************************/
|
|
static int set_constant_value(expr_node *atom)
|
|
{
|
|
int dse, tim, val, prev_val, h, m, ampm, r;
|
|
size_t len;
|
|
char const *s = DBufValue(&ExprBuf);
|
|
atom->u.value.type = ERR_TYPE;
|
|
atom->type = N_CONSTANT;
|
|
|
|
if (!*s) {
|
|
Eprint("%s", GetErr(E_EOLN));
|
|
return E_EOLN;
|
|
}
|
|
ampm = 0;
|
|
if (*s == '\"') { /* It's a literal string "*/
|
|
len = strlen(s)-1;
|
|
if (len <= SHORT_NAME_BUF) {
|
|
atom->type = N_SHORT_STR;
|
|
strncpy(atom->u.name, s+1, len-1);
|
|
atom->u.name[len-1] = 0;
|
|
return OK;
|
|
}
|
|
atom->u.value.type = STR_TYPE;
|
|
atom->u.value.v.str = malloc(len);
|
|
if (! atom->u.value.v.str) {
|
|
atom->u.value.type = ERR_TYPE;
|
|
return E_NO_MEM;
|
|
}
|
|
strncpy(atom->u.value.v.str, s+1, len-1);
|
|
*(atom->u.value.v.str+len-1) = 0;
|
|
return OK;
|
|
} else if (*s == '\'') { /* It's a literal date */
|
|
s++;
|
|
if ((r=ParseLiteralDateOrTime(&s, &dse, &tim)) != 0) {
|
|
Eprint("%s: %s", GetErr(r), DBufValue(&ExprBuf));
|
|
return r;
|
|
}
|
|
if (*s != '\'') {
|
|
if (dse != NO_DATE) {
|
|
Eprint("%s: %s", GetErr(E_BAD_DATE), DBufValue(&ExprBuf));
|
|
return E_BAD_DATE;
|
|
} else {
|
|
Eprint("%s: %s", GetErr(E_BAD_TIME), DBufValue(&ExprBuf));
|
|
return E_BAD_TIME;
|
|
}
|
|
}
|
|
if (tim == NO_TIME) {
|
|
atom->u.value.type = DATE_TYPE;
|
|
atom->u.value.v.val = dse;
|
|
} else if (dse == NO_DATE) {
|
|
atom->u.value.type = TIME_TYPE;
|
|
atom->u.value.v.val = tim;
|
|
} else {
|
|
atom->u.value.type = DATETIME_TYPE;
|
|
atom->u.value.v.val = (dse * MINUTES_PER_DAY) + tim;
|
|
}
|
|
return OK;
|
|
} else if (isdigit(*s)) { /* It's a number or time */
|
|
atom->u.value.type = INT_TYPE;
|
|
val = 0;
|
|
prev_val = 0;
|
|
while (*s && isdigit(*s)) {
|
|
val *= 10;
|
|
val += (*s++ - '0');
|
|
if (val < prev_val) {
|
|
/* We overflowed */
|
|
return E_2HIGH;
|
|
}
|
|
prev_val = val;
|
|
}
|
|
if (*s == ':' || *s == '.' || *s == TimeSep) { /* Must be a literal time */
|
|
s++;
|
|
if (!isdigit(*s)) {
|
|
Eprint("%s: `%s'", GetErr(E_BAD_TIME), DBufValue(&ExprBuf));
|
|
return E_BAD_TIME;
|
|
}
|
|
h = val;
|
|
m = 0;
|
|
while (isdigit(*s)) {
|
|
m *= 10;
|
|
m += *s - '0';
|
|
s++;
|
|
}
|
|
/* Check for p[m] or a[m] */
|
|
if (*s == 'A' || *s == 'a' || *s == 'P' || *s == 'p') {
|
|
ampm = tolower(*s);
|
|
s++;
|
|
if (*s == 'm' || *s == 'M') {
|
|
s++;
|
|
}
|
|
}
|
|
if (*s || h>23 || m>59) {
|
|
Eprint("%s: `%s'", GetErr(E_BAD_TIME), DBufValue(&ExprBuf));
|
|
return E_BAD_TIME;
|
|
}
|
|
if (ampm) {
|
|
if (h < 1 || h > 12) {
|
|
Eprint("%s: `%s'", GetErr(E_BAD_TIME), DBufValue(&ExprBuf));
|
|
return E_BAD_TIME;
|
|
}
|
|
if (ampm == 'a') {
|
|
if (h == 12) {
|
|
h = 0;
|
|
}
|
|
} else if (ampm == 'p') {
|
|
if (h < 12) {
|
|
h += 12;
|
|
}
|
|
}
|
|
}
|
|
atom->u.value.type = TIME_TYPE;
|
|
atom->u.value.v.val = h*60 + m;
|
|
return OK;
|
|
}
|
|
/* Not a time - must be a number */
|
|
if (*s) {
|
|
Eprint("%s: `%s'", GetErr(E_BAD_NUMBER), DBufValue(&ExprBuf));
|
|
return E_BAD_NUMBER;
|
|
}
|
|
atom->u.value.type = INT_TYPE;
|
|
atom->u.value.v.val = val;
|
|
return OK;
|
|
}
|
|
atom->u.value.type = ERR_TYPE;
|
|
Eprint("`%s': %s", DBufValue(&ExprBuf), GetErr(E_ILLEGAL_CHAR));
|
|
return E_ILLEGAL_CHAR;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* make_atom - create a constant, variable, system variable */
|
|
/* or local variable node. */
|
|
/* */
|
|
/***************************************************************/
|
|
static int make_atom(expr_node *atom, Var *locals)
|
|
{
|
|
int r;
|
|
int i = 0;
|
|
Var *v = locals;
|
|
char const *s = DBufValue(&ExprBuf);
|
|
/* Variable */
|
|
if (isalpha(*s) || *s == '_') {
|
|
while(v) {
|
|
if (! StrinCmp(s, v->name, VAR_NAME_LEN)) {
|
|
atom->type = N_LOCAL_VAR;
|
|
atom->u.arg = i;
|
|
return OK;
|
|
}
|
|
v = v->link.next;
|
|
i++;
|
|
}
|
|
if (strlen(s) < SHORT_NAME_BUF) {
|
|
atom->type = N_SHORT_VAR;
|
|
strcpy(atom->u.name, s);
|
|
} else {
|
|
if (set_long_name(atom, s) != OK) {
|
|
return E_NO_MEM;
|
|
}
|
|
atom->type = N_VARIABLE;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/* System Variable */
|
|
if (*(s) == '$' && isalpha(*(s+1))) {
|
|
if (!FindSysVar(s+1)) {
|
|
Eprint("%s: `%s'", GetErr(E_NOSUCH_VAR), s);
|
|
return E_NOSUCH_VAR;
|
|
}
|
|
if (strlen(s+1) < SHORT_NAME_BUF) {
|
|
atom->type = N_SHORT_SYSVAR;
|
|
strcpy(atom->u.name, s+1);
|
|
} else {
|
|
if (set_long_name(atom, s+1) != OK) {
|
|
return E_NO_MEM;
|
|
}
|
|
atom->type = N_SYSVAR;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/* Constant */
|
|
r = set_constant_value(atom);
|
|
if (r != OK) {
|
|
atom->type = N_ERROR;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* Parse an atom. */
|
|
/* */
|
|
/* ATOM: '(' EXPR ')' | */
|
|
/* CONSTANT | */
|
|
/* VAR | */
|
|
/* FUNCTION_CALL */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_atom(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
char const *s;
|
|
CHECK_PARSE_LEVEL();
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) return NULL;
|
|
|
|
if (TOKEN_IS("(")) {
|
|
/* Parenthesiszed expession: '(' EXPR ')' */
|
|
|
|
/* Pull off the peeked token */
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return NULL;
|
|
}
|
|
node = parse_expression_aux(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return NULL;
|
|
}
|
|
if (TOKEN_ISNOT(")")) {
|
|
*r = E_MISS_RIGHT_PAREN;
|
|
return free_expr_tree(node);
|
|
}
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/* Check that it's a valid ID or constant */
|
|
s = DBufValue(&ExprBuf);
|
|
if (!*s) {
|
|
Eprint("%s", GetErr(E_EOLN));
|
|
*r = E_EOLN;
|
|
return NULL;
|
|
}
|
|
if (!ISID(*s) &&
|
|
*s != '$' &&
|
|
*s != '"' &&
|
|
*s != '\'') {
|
|
Eprint("%s `%c'", GetErr(E_ILLEGAL_CHAR), *s);
|
|
*r = E_ILLEGAL_CHAR;
|
|
return NULL;
|
|
}
|
|
|
|
/* Is it a function call? */
|
|
if (*(s + DBufLen(&ExprBuf) - 1) == '(') {
|
|
return parse_function_call(e, r, locals, level+1);
|
|
}
|
|
|
|
/* It's a constant or a variable reference */
|
|
char const *olds = *e;
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) return NULL;
|
|
node = alloc_expr_node(r);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
*r = make_atom(node, locals);
|
|
if (*r != OK) {
|
|
/* Preserve location for error position when we print ^-- here */
|
|
*e = olds;
|
|
return free_expr_tree(node);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_factor - parse a factor */
|
|
/* */
|
|
/* FACTOR_EXP: '-' FACTOR_EXP | */
|
|
/* '!' FACTOR_EXP | */
|
|
/* '+' FACTOR_EXP */
|
|
/* ATOM */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_factor(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *factor_node;
|
|
char op;
|
|
CHECK_PARSE_LEVEL();
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) {
|
|
return NULL;
|
|
}
|
|
if (TOKEN_IS("!") || TOKEN_IS("-") || TOKEN_IS("+")) {
|
|
if (TOKEN_IS("!")) {
|
|
op = '!';
|
|
} else if (TOKEN_IS("-")) {
|
|
op = '-';
|
|
} else {
|
|
op = '+';
|
|
}
|
|
/* Pull off the peeked token */
|
|
GET_TOKEN();
|
|
node = parse_factor(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return NULL;
|
|
}
|
|
|
|
/* Ignore unary plus operator */
|
|
if (op == '+') {
|
|
return node;
|
|
}
|
|
|
|
/* Optimize '-' or '!' followed by integer constant */
|
|
if (node->type == N_CONSTANT &&node->u.value.type == INT_TYPE) {
|
|
if (op == '-') {
|
|
node->u.value.v.val = - node->u.value.v.val;
|
|
} else {
|
|
node->u.value.v.val = ! node->u.value.v.val;
|
|
}
|
|
return node;
|
|
}
|
|
factor_node = alloc_expr_node(r);
|
|
if (!factor_node) {
|
|
free_expr_tree(node);
|
|
return NULL;
|
|
}
|
|
factor_node->type = N_OPERATOR;
|
|
if (op == '!') {
|
|
factor_node->u.operator_func = logical_not;
|
|
} else {
|
|
factor_node->u.operator_func = unary_minus;
|
|
}
|
|
add_child(factor_node, node);
|
|
return factor_node;
|
|
}
|
|
return parse_atom(e, r, locals, level+1);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_term - parse a term */
|
|
/* */
|
|
/* TERM_EXP: FACTOR_EXP | */
|
|
/* FACTOR_EXP '*' TERM_EXP | */
|
|
/* FACTOR_EXP '/' TERM_EXP | */
|
|
/* FACTOR_EXP '%' TERM_EXP */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_term_expr(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *term_node;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = parse_factor(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
|
|
while(TOKEN_IS("*") || TOKEN_IS("/") || TOKEN_IS("%")) {
|
|
term_node = alloc_expr_node(r);
|
|
if (!term_node) {
|
|
return free_expr_tree(node);
|
|
}
|
|
term_node->type = N_OPERATOR;
|
|
if (TOKEN_IS("*")) {
|
|
term_node->u.operator_func = multiply;
|
|
} else if (TOKEN_IS("/")) {
|
|
term_node->u.operator_func = divide;
|
|
} else {
|
|
term_node->u.operator_func = do_mod;
|
|
}
|
|
add_child(term_node, node);
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(term_node);
|
|
}
|
|
node = parse_factor(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(term_node);
|
|
}
|
|
add_child(term_node, node);
|
|
node = term_node;
|
|
*r = PEEK_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(term_node);
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_cmp_expr - parse a cmp_expr */
|
|
/* */
|
|
/* CMP_EXP: TERM_EXP | */
|
|
/* TERM_EXP '+' CMP_EXP | */
|
|
/* TERM_EXP '-' CMP_EXP */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_cmp_expr(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *cmp_node;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = parse_term_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
while(TOKEN_IS("+") || TOKEN_IS("-")) {
|
|
cmp_node = alloc_expr_node(r);
|
|
if (!cmp_node) {
|
|
return free_expr_tree(node);
|
|
}
|
|
cmp_node->type = N_OPERATOR;
|
|
if (TOKEN_IS("+")) {
|
|
cmp_node->u.operator_func = add;
|
|
} else {
|
|
cmp_node->u.operator_func = subtract;
|
|
}
|
|
add_child(cmp_node, node);
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(cmp_node);
|
|
}
|
|
node = parse_term_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(cmp_node);
|
|
}
|
|
add_child(cmp_node, node);
|
|
node = cmp_node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_eq_expr - parse an eq_expr */
|
|
/* */
|
|
/* EQ_EXP: CMP_EXP | */
|
|
/* CMP_EXP '<' EQ_EXP | */
|
|
/* CMP_EXP '>' EQ_EXP | */
|
|
/* CMP_EXP '<=' EQ_EXP | */
|
|
/* CMP_EXP '<=' EQ_EXP */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_eq_expr(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *eq_node;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = parse_cmp_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
while(TOKEN_IS("<=") || TOKEN_IS(">=") || TOKEN_IS("<") || TOKEN_IS(">")) {
|
|
eq_node = alloc_expr_node(r);
|
|
if (!eq_node) {
|
|
return free_expr_tree(node);
|
|
}
|
|
eq_node->type = N_OPERATOR;
|
|
if (TOKEN_IS("<=")) {
|
|
eq_node->u.operator_func = compare_le;
|
|
} else if (TOKEN_IS(">=")) {
|
|
eq_node->u.operator_func = compare_ge;
|
|
} else if (TOKEN_IS("<")) {
|
|
eq_node->u.operator_func = compare_lt;
|
|
} else {
|
|
eq_node->u.operator_func = compare_gt;
|
|
}
|
|
add_child(eq_node, node);
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(eq_node);
|
|
}
|
|
node = parse_cmp_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
free_expr_tree(eq_node);
|
|
return free_expr_tree(node);
|
|
}
|
|
add_child(eq_node, node);
|
|
node = eq_node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_and_expr - parse an and_expr */
|
|
/* */
|
|
/* AND_EXP: EQ_EXP | */
|
|
/* EQ_EXP '==' AND_EXP | */
|
|
/* EQ_EXP '!=' AND_EXP */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_and_expr(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *and_node;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = parse_eq_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
while(TOKEN_IS("==") || TOKEN_IS("!=")) {
|
|
and_node = alloc_expr_node(r);
|
|
if (!and_node) {
|
|
return free_expr_tree(node);
|
|
}
|
|
and_node->type = N_OPERATOR;
|
|
if (TOKEN_IS("==")) {
|
|
and_node->u.operator_func = compare_eq;
|
|
} else {
|
|
and_node->u.operator_func = compare_ne;
|
|
}
|
|
add_child(and_node, node);
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(and_node);
|
|
}
|
|
node = parse_eq_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
return free_expr_tree(and_node);
|
|
}
|
|
add_child(and_node, node);
|
|
node = and_node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_or_expr - parse an or_expr */
|
|
/* */
|
|
/* OR_EXP: AND_EXP | */
|
|
/* AND_EXP '&&' OR_EXP */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_or_expr(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *logand_node;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = parse_and_expr(e, r, locals, level+1);
|
|
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
|
|
while (TOKEN_IS("&&")) {
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
logand_node = alloc_expr_node(r);
|
|
if (!logand_node) {
|
|
return free_expr_tree(node);
|
|
}
|
|
logand_node->type = N_OPERATOR;
|
|
logand_node->u.operator_func = logical_and;
|
|
add_child(logand_node, node);
|
|
node = parse_and_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
free_expr_tree(logand_node);
|
|
return free_expr_tree(node);
|
|
}
|
|
add_child(logand_node, node);
|
|
node = logand_node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_expression_aux - parse an EXPR */
|
|
/* */
|
|
/* EXPR: OR_EXP | */
|
|
/* OR_EXP '||' EXPR */
|
|
/* */
|
|
/***************************************************************/
|
|
static expr_node *parse_expression_aux(char const **e, int *r, Var *locals, int level)
|
|
{
|
|
expr_node *node;
|
|
expr_node *logor_node;
|
|
CHECK_PARSE_LEVEL();
|
|
|
|
node = parse_or_expr(e, r, locals, level+1);
|
|
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
while (TOKEN_IS("||")) {
|
|
*r = GET_TOKEN();
|
|
if (*r != OK) {
|
|
return free_expr_tree(node);
|
|
}
|
|
logor_node = alloc_expr_node(r);
|
|
if (!logor_node) {
|
|
return free_expr_tree(node);
|
|
}
|
|
logor_node->type = N_OPERATOR;
|
|
logor_node->u.operator_func = logical_or;
|
|
add_child(logor_node, node);
|
|
node = parse_or_expr(e, r, locals, level+1);
|
|
if (*r != OK) {
|
|
free_expr_tree(logor_node);
|
|
return free_expr_tree(node);
|
|
}
|
|
add_child(logor_node, node);
|
|
node = logor_node;
|
|
}
|
|
return node;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* parse_expression - top-level expression-parsing function */
|
|
/* */
|
|
/* *e points to string being parsed; *e will be updated */
|
|
/* r points to a location for the return code (OK or an error) */
|
|
/* locals is an array of local function arguments, if any */
|
|
/* */
|
|
/* Returns a parsed expr_node or NULL on error */
|
|
/* */
|
|
/***************************************************************/
|
|
expr_node *parse_expression(char const **e, int *r, Var *locals)
|
|
{
|
|
char const *orig = *e;
|
|
char const *o2 = *e;
|
|
char const *end_of_expr;
|
|
if (ExpressionEvaluationDisabled) {
|
|
*r = E_EXPR_DISABLED;
|
|
return NULL;
|
|
}
|
|
|
|
expr_node *node = parse_expression_aux(e, r, locals, 0);
|
|
if (DebugFlag & DB_PARSE_EXPR) {
|
|
fprintf(ErrFp, "Parsed expression: ");
|
|
while (*orig && orig != *e) {
|
|
putc(*orig, ErrFp);
|
|
orig++;
|
|
}
|
|
putc('\n', ErrFp);
|
|
if (*r != OK) {
|
|
fprintf(ErrFp, " => Error: %s\n", GetErr(*r));
|
|
} else {
|
|
fprintf(ErrFp, " => ");
|
|
print_expr_tree(node, ErrFp);
|
|
fprintf(ErrFp, "\n");
|
|
}
|
|
if (**e && (**e != ']')) {
|
|
fprintf(ErrFp, " Unparsed: %s\n", *e);
|
|
}
|
|
}
|
|
if (!SuppressErrorOutputInCatch) {
|
|
if (*r == E_EXPECT_COMMA ||
|
|
*r == E_MISS_RIGHT_PAREN ||
|
|
*r == E_EXPECTING_EOL ||
|
|
*r == E_2MANY_ARGS ||
|
|
*r == E_2FEW_ARGS ||
|
|
*r == E_PARSE_ERR ||
|
|
*r == E_EOLN ||
|
|
*r == E_BAD_NUMBER ||
|
|
*r == E_BAD_DATE ||
|
|
*r == E_BAD_TIME ||
|
|
*r == E_ILLEGAL_CHAR) {
|
|
end_of_expr = find_end_of_expr(orig);
|
|
while (**e && isempty(**e)) {
|
|
(*e)++;
|
|
}
|
|
while (*orig && ((orig < end_of_expr) || (orig <= *e))) {
|
|
if (*orig == '\n') {
|
|
fprintf(ErrFp, " ");
|
|
} else {
|
|
fprintf(ErrFp, "%c", *orig);
|
|
}
|
|
orig++;
|
|
}
|
|
fprintf(ErrFp, "\n");
|
|
orig = o2;
|
|
while (*orig && (orig < *e || isspace(*orig))) {
|
|
orig++;
|
|
fprintf(ErrFp, " ");
|
|
}
|
|
fprintf(ErrFp, "^-- %s\n", tr("here"));
|
|
}
|
|
}
|
|
return node;
|
|
}
|
|
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* print_kids - print all of this node's children to a file, */
|
|
/* recursively. Used for debugging. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void print_kids(expr_node *node, FILE *fp)
|
|
{
|
|
int done = 0;
|
|
expr_node *kid = node->child;
|
|
while(kid) {
|
|
if (done) {
|
|
fprintf(fp, " ");
|
|
}
|
|
done=1;
|
|
print_expr_tree(kid, fp);
|
|
kid = kid->sibling;
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* print_expr_tree - print the entire expression tree to a */
|
|
/* file. Used for debugging (the "-ds" flag.) */
|
|
/* */
|
|
/***************************************************************/
|
|
static void print_expr_tree(expr_node *node, FILE *fp)
|
|
{
|
|
if (!node) {
|
|
return;
|
|
}
|
|
switch(node->type) {
|
|
case N_CONSTANT:
|
|
PrintValue(&(node->u.value), fp);
|
|
return;
|
|
case N_SHORT_STR:
|
|
fprintf(fp, "\"%s\"", node->u.name);
|
|
return;
|
|
case N_SHORT_VAR:
|
|
fprintf(fp, "%s", node->u.name);
|
|
return;
|
|
case N_VARIABLE:
|
|
fprintf(fp, "%s", node->u.value.v.str);
|
|
return;
|
|
case N_SHORT_SYSVAR:
|
|
fprintf(fp, "$%s", node->u.name);
|
|
return;
|
|
case N_SYSVAR:
|
|
fprintf(fp, "$%s", node->u.value.v.str);
|
|
return;
|
|
case N_LOCAL_VAR:
|
|
fprintf(fp, "arg[%d]", node->u.arg);
|
|
return;
|
|
case N_BUILTIN_FUNC:
|
|
fprintf(fp, "(%c%s",
|
|
toupper(*(node->u.builtin_func->name)),
|
|
node->u.builtin_func->name+1);
|
|
if (node->child) fprintf(fp, " ");
|
|
print_kids(node, fp);
|
|
fprintf(fp, ")");
|
|
return;
|
|
case N_SHORT_USER_FUNC:
|
|
fprintf(fp, "(%s", node->u.name);
|
|
if (node->child) fprintf(fp, " ");
|
|
print_kids(node, fp);
|
|
fprintf(fp, ")");
|
|
return;
|
|
case N_USER_FUNC:
|
|
fprintf(fp, "(%s", node->u.value.v.str);
|
|
if (node->child) fprintf(fp, " ");
|
|
print_kids(node, fp);
|
|
fprintf(fp, ")");
|
|
return;
|
|
case N_OPERATOR:
|
|
fprintf(fp, "(%s ", get_operator_name(node));
|
|
print_kids(node, fp);
|
|
fprintf(fp, ")");
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* get_operator_name - given an expr_node of type N_OPERATOR, */
|
|
/* return a string representation of the operator. */
|
|
/* */
|
|
/***************************************************************/
|
|
static char const *get_operator_name(expr_node *node)
|
|
{
|
|
int (*f)(expr_node *node, Value *locals, Value *ans, int *nonconst) = node->u.operator_func;
|
|
if (f == logical_not) return "!";
|
|
else if (f == unary_minus) return "-";
|
|
else if (f == multiply) return "*";
|
|
else if (f == divide) return "/";
|
|
else if (f == do_mod) return "%";
|
|
else if (f == add) return "+";
|
|
else if (f == subtract) return "-";
|
|
else if (f == compare_le) return "<=";
|
|
else if (f == compare_ge) return ">=";
|
|
else if (f == compare_lt) return "<";
|
|
else if (f == compare_gt) return ">";
|
|
else if (f == compare_eq) return "==";
|
|
else if (f == compare_ne) return "!=";
|
|
else if (f == logical_and) return "&&";
|
|
else if (f == logical_or) return "||";
|
|
else return "UNKNOWN_OPERATOR";
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* EvalExpr - parse and evaluate an expression. */
|
|
/* Evaluate an expression. Return 0 if OK, non-zero if error */
|
|
/* Put the result into value pointed to by v. */
|
|
/* */
|
|
/***************************************************************/
|
|
int EvalExpr(char const **e, Value *v, ParsePtr p)
|
|
{
|
|
int r;
|
|
int nonconst = 0;
|
|
|
|
/* Parse */
|
|
expr_node *n = parse_expression(e, &r, NULL);
|
|
if (r != OK) {
|
|
return r;
|
|
}
|
|
|
|
/* Evaluate */
|
|
r = evaluate_expression(n, NULL, v, &nonconst);
|
|
|
|
/* Throw away the parsed tree */
|
|
free_expr_tree(n);
|
|
if (r != OK) {
|
|
return r;
|
|
}
|
|
if (nonconst) {
|
|
if (p) {
|
|
p->nonconst_expr = 1;
|
|
}
|
|
}
|
|
return r;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* PrintValue */
|
|
/* */
|
|
/* Print or stringify a value for debugging purposes. */
|
|
/* */
|
|
/***************************************************************/
|
|
static DynamicBuffer printbuf = {NULL, 0, 0, ""};
|
|
|
|
#define PV_PUTC(fp, c) do { if (fp) { putc((c), fp); } else { DBufPutc(&printbuf, (c)); } } while(0);
|
|
char const *PrintValue (Value *v, FILE *fp)
|
|
{
|
|
int y, m, d;
|
|
unsigned char const *s;
|
|
char pvbuf[512];
|
|
if (!fp) {
|
|
/* It's OK to DBufFree an uninitialized *BUT STATIC* dynamic buffer */
|
|
DBufFree(&printbuf);
|
|
}
|
|
|
|
if (v->type == STR_TYPE) {
|
|
s = (unsigned char const *) v->v.str;
|
|
PV_PUTC(fp, '"');
|
|
for (y=0; y<MAX_PRT_LEN && *s; y++) {
|
|
switch(*s) {
|
|
case '\a': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'a'); break;
|
|
case '\b': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'b'); break;
|
|
case '\f': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'f'); break;
|
|
case '\n': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'n'); break;
|
|
case '\r': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'r'); break;
|
|
case '\t': PV_PUTC(fp, '\\'); PV_PUTC(fp, 't'); break;
|
|
case '\v': PV_PUTC(fp, '\\'); PV_PUTC(fp, 'v'); break;
|
|
case '"': PV_PUTC(fp, '\\'); PV_PUTC(fp, '"'); break;
|
|
case '\\': PV_PUTC(fp, '\\'); PV_PUTC(fp, '\\'); break;
|
|
default:
|
|
if (*s < 32) {
|
|
if (fp) {
|
|
fprintf(fp, "\\x%02x", (unsigned int) *s);
|
|
} else {
|
|
snprintf(pvbuf, sizeof(pvbuf), "\\x%02x", (unsigned int) *s);
|
|
DBufPuts(&printbuf, pvbuf);
|
|
}
|
|
} else {
|
|
PV_PUTC(fp, *s); break;
|
|
}
|
|
}
|
|
s++;
|
|
}
|
|
PV_PUTC(fp, '"');
|
|
if (*s) {
|
|
if (fp) {
|
|
fprintf(fp, "...");
|
|
} else {
|
|
DBufPuts(&printbuf, "...");
|
|
}
|
|
}
|
|
}
|
|
else if (v->type == INT_TYPE) {
|
|
if (fp) {
|
|
fprintf(fp, "%d", v->v.val);
|
|
} else {
|
|
snprintf(pvbuf, sizeof(pvbuf), "%d", v->v.val);
|
|
DBufPuts(&printbuf, pvbuf);
|
|
}
|
|
} else if (v->type == TIME_TYPE) {
|
|
if (fp) {
|
|
fprintf(fp, "%02d%c%02d", v->v.val / 60,
|
|
TimeSep, v->v.val % 60);
|
|
} else {
|
|
snprintf(pvbuf, sizeof(pvbuf), "%02d%c%02d", v->v.val / 60,
|
|
TimeSep, v->v.val % 60);
|
|
DBufPuts(&printbuf, pvbuf);
|
|
}
|
|
} else if (v->type == DATE_TYPE) {
|
|
FromDSE(v->v.val, &y, &m, &d);
|
|
if (fp) {
|
|
fprintf(fp, "%04d%c%02d%c%02d", y, DateSep, m+1, DateSep, d);
|
|
} else {
|
|
snprintf(pvbuf, sizeof(pvbuf), "%04d%c%02d%c%02d", y, DateSep, m+1, DateSep, d);
|
|
DBufPuts(&printbuf, pvbuf);
|
|
}
|
|
} else if (v->type == DATETIME_TYPE) {
|
|
FromDSE(v->v.val / MINUTES_PER_DAY, &y, &m, &d);
|
|
if (fp) {
|
|
fprintf(fp, "%04d%c%02d%c%02d%c%02d%c%02d", y, DateSep, m+1, DateSep, d, DateTimeSep,
|
|
(v->v.val % MINUTES_PER_DAY) / 60, TimeSep, (v->v.val % MINUTES_PER_DAY) % 60);
|
|
} else {
|
|
snprintf(pvbuf, sizeof(pvbuf), "%04d%c%02d%c%02d%c%02d%c%02d", y, DateSep, m+1, DateSep, d, DateTimeSep,
|
|
(v->v.val % MINUTES_PER_DAY) / 60, TimeSep, (v->v.val % MINUTES_PER_DAY) % 60);
|
|
DBufPuts(&printbuf, pvbuf);
|
|
}
|
|
} else {
|
|
if (fp) {
|
|
fprintf(fp, "ERR");
|
|
} else {
|
|
DBufPuts(&printbuf, "ERR");
|
|
}
|
|
}
|
|
if (fp) {
|
|
return NULL;
|
|
} else {
|
|
return DBufValue(&printbuf);
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* CopyValue */
|
|
/* */
|
|
/* Copy a value. If value is a string, strdups the string. */
|
|
/* */
|
|
/***************************************************************/
|
|
int CopyValue(Value *dest, const Value *src)
|
|
{
|
|
dest->type = ERR_TYPE;
|
|
if (src->type == STR_TYPE) {
|
|
dest->v.str = StrDup(src->v.str);
|
|
if (!dest->v.str) return E_NO_MEM;
|
|
} else {
|
|
dest->v.val = src->v.val;
|
|
}
|
|
dest->type = src->type;
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ParseLiteralTime - parse a literal time like 11:15 or */
|
|
/* 4:00PM */
|
|
/* */
|
|
/***************************************************************/
|
|
static int ParseLiteralTime(char const **s, int *tim)
|
|
{
|
|
int h=0;
|
|
int m=0;
|
|
int ampm=0;
|
|
if (!isdigit(**s)) return E_BAD_TIME;
|
|
while(isdigit(**s)) {
|
|
h *= 10;
|
|
h += *(*s)++ - '0';
|
|
}
|
|
if (**s != ':' && **s != '.' && **s != TimeSep) return E_BAD_TIME;
|
|
(*s)++;
|
|
if (!isdigit(**s)) return E_BAD_TIME;
|
|
while(isdigit(**s)) {
|
|
m *= 10;
|
|
m += *(*s)++ - '0';
|
|
}
|
|
/* Check for p[m] or a[m] */
|
|
if (**s == 'A' || **s == 'a' || **s == 'P' || **s == 'p') {
|
|
ampm = tolower(**s);
|
|
(*s)++;
|
|
if (**s == 'm' || **s == 'M') {
|
|
(*s)++;
|
|
}
|
|
}
|
|
if (h>23 || m>59) return E_BAD_TIME;
|
|
if (ampm) {
|
|
if (h < 1 || h > 12) return E_BAD_TIME;
|
|
if (ampm == 'a') {
|
|
if (h == 12) {
|
|
h = 0;
|
|
}
|
|
} else if (ampm == 'p') {
|
|
if (h < 12) {
|
|
h += 12;
|
|
}
|
|
}
|
|
}
|
|
*tim = h * 60 + m;
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* ParseLiteralDateOrTime */
|
|
/* */
|
|
/* Parse a literal date or datetime. Return result in dse */
|
|
/* and tim; update s. */
|
|
/* */
|
|
/***************************************************************/
|
|
int ParseLiteralDateOrTime(char const **s, int *dse, int *tim)
|
|
{
|
|
int y, m, d;
|
|
int r;
|
|
|
|
char const *orig_s = *s;
|
|
|
|
y=0; m=0; d=0;
|
|
|
|
*tim = NO_TIME;
|
|
*dse = NO_DATE;
|
|
if (!isdigit(**s)) return E_BAD_DATE;
|
|
while (isdigit(**s)) {
|
|
y *= 10;
|
|
y += *(*s)++ - '0';
|
|
}
|
|
if (**s == ':' || **s == '.' || **s == TimeSep) {
|
|
*s = orig_s;
|
|
return ParseLiteralTime(s, tim);
|
|
}
|
|
if (**s != '/' && **s != '-' && **s != DateSep) return E_BAD_DATE;
|
|
(*s)++;
|
|
if (!isdigit(**s)) return E_BAD_DATE;
|
|
while (isdigit(**s)) {
|
|
m *= 10;
|
|
m += *(*s)++ - '0';
|
|
}
|
|
m--;
|
|
if (**s != '/' && **s != '-' && **s != DateSep) return E_BAD_DATE;
|
|
(*s)++;
|
|
if (!isdigit(**s)) return E_BAD_DATE;
|
|
while (isdigit(**s)) {
|
|
d *= 10;
|
|
d += *(*s)++ - '0';
|
|
}
|
|
if (!DateOK(y, m, d)) return E_BAD_DATE;
|
|
|
|
*dse = DSE(y, m, d);
|
|
|
|
/* Do we have a time part as well? */
|
|
if (**s == ' ' || **s == '@' || **s == 'T' || **s == 't') {
|
|
(*s)++;
|
|
r = ParseLiteralTime(s, tim);
|
|
if (r != OK) return r;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoCoerce - actually coerce a value to the specified type. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoCoerce(char type, Value *v)
|
|
{
|
|
int h, d, m, y, i, k;
|
|
char const *s;
|
|
|
|
char coerce_buf[128];
|
|
|
|
/* Do nothing if value is already the right type */
|
|
if (type == v->type) return OK;
|
|
|
|
switch(type) {
|
|
case DATETIME_TYPE:
|
|
switch(v->type) {
|
|
case INT_TYPE:
|
|
v->type = DATETIME_TYPE;
|
|
return OK;
|
|
case DATE_TYPE:
|
|
v->type = DATETIME_TYPE;
|
|
v->v.val *= MINUTES_PER_DAY;
|
|
return OK;
|
|
case STR_TYPE:
|
|
s = v->v.str;
|
|
if (ParseLiteralDateOrTime(&s, &i, &m)) return E_CANT_COERCE;
|
|
if (i == NO_DATE) return E_CANT_COERCE;
|
|
if (*s) return E_CANT_COERCE;
|
|
v->type = DATETIME_TYPE;
|
|
free(v->v.str);
|
|
if (m == NO_TIME) m = 0;
|
|
v->v.val = i * MINUTES_PER_DAY + m;
|
|
return OK;
|
|
default:
|
|
return E_CANT_COERCE;
|
|
}
|
|
case STR_TYPE:
|
|
switch(v->type) {
|
|
case INT_TYPE: snprintf(coerce_buf, sizeof(coerce_buf), "%d", v->v.val); break;
|
|
case TIME_TYPE: snprintf(coerce_buf, sizeof(coerce_buf), "%02d%c%02d", v->v.val / 60,
|
|
TimeSep, v->v.val % 60);
|
|
break;
|
|
case DATE_TYPE: FromDSE(v->v.val, &y, &m, &d);
|
|
snprintf(coerce_buf, sizeof(coerce_buf), "%04d%c%02d%c%02d",
|
|
y, DateSep, m+1, DateSep, d);
|
|
break;
|
|
case DATETIME_TYPE:
|
|
i = v->v.val / MINUTES_PER_DAY;
|
|
FromDSE(i, &y, &m, &d);
|
|
k = v->v.val % MINUTES_PER_DAY;
|
|
h = k / 60;
|
|
i = k % 60;
|
|
snprintf(coerce_buf, sizeof(coerce_buf), "%04d%c%02d%c%02d%c%02d%c%02d",
|
|
y, DateSep, m+1, DateSep, d, DateTimeSep, h, TimeSep, i);
|
|
break;
|
|
default: return E_CANT_COERCE;
|
|
}
|
|
v->type = STR_TYPE;
|
|
v->v.str = StrDup(coerce_buf);
|
|
if (!v->v.str) {
|
|
v->type = ERR_TYPE;
|
|
return E_NO_MEM;
|
|
}
|
|
return OK;
|
|
|
|
case INT_TYPE:
|
|
i = 0;
|
|
m = 1;
|
|
switch(v->type) {
|
|
case STR_TYPE:
|
|
s = v->v.str;
|
|
if (*s == '-') {
|
|
m = -1;
|
|
s++;
|
|
}
|
|
while(*s && isdigit(*s)) {
|
|
i *= 10;
|
|
i += (*s++) - '0';
|
|
}
|
|
if (*s) {
|
|
free (v->v.str);
|
|
v->type = ERR_TYPE;
|
|
return E_CANT_COERCE;
|
|
}
|
|
free(v->v.str);
|
|
v->type = INT_TYPE;
|
|
v->v.val = i * m;
|
|
return OK;
|
|
|
|
case DATE_TYPE:
|
|
case TIME_TYPE:
|
|
case DATETIME_TYPE:
|
|
v->type = INT_TYPE;
|
|
return OK;
|
|
|
|
default: return E_CANT_COERCE;
|
|
}
|
|
|
|
case DATE_TYPE:
|
|
switch(v->type) {
|
|
case INT_TYPE:
|
|
if(v->v.val >= 0) {
|
|
v->type = DATE_TYPE;
|
|
return OK;
|
|
} else return E_2LOW;
|
|
|
|
case STR_TYPE:
|
|
s = v->v.str;
|
|
if (ParseLiteralDateOrTime(&s, &i, &m)) return E_CANT_COERCE;
|
|
if (i == NO_DATE) return E_CANT_COERCE;
|
|
if (*s) return E_CANT_COERCE;
|
|
v->type = DATE_TYPE;
|
|
free(v->v.str);
|
|
v->v.val = i;
|
|
return OK;
|
|
|
|
case DATETIME_TYPE:
|
|
v->type = DATE_TYPE;
|
|
v->v.val /= MINUTES_PER_DAY;
|
|
return OK;
|
|
|
|
default: return E_CANT_COERCE;
|
|
}
|
|
|
|
case TIME_TYPE:
|
|
switch(v->type) {
|
|
case INT_TYPE:
|
|
case DATETIME_TYPE:
|
|
v->type = TIME_TYPE;
|
|
v->v.val %= MINUTES_PER_DAY;
|
|
if (v->v.val < 0) v->v.val += MINUTES_PER_DAY;
|
|
return OK;
|
|
|
|
case STR_TYPE:
|
|
s = v->v.str;
|
|
i=0; /* Avoid compiler warning */
|
|
if (ParseLiteralTime(&s, &i)) return E_CANT_COERCE;
|
|
if (*s) return E_CANT_COERCE;
|
|
v->type = TIME_TYPE;
|
|
free(v->v.str);
|
|
v->v.val = i;
|
|
return OK;
|
|
|
|
default: return E_CANT_COERCE;
|
|
}
|
|
default: return E_CANT_COERCE;
|
|
}
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* print_expr_nodes_stats - print statistics about expr_node */
|
|
/* allocation. */
|
|
/* */
|
|
/* This is a debugging routine that prints data about */
|
|
/* expr_node allocation on program exit */
|
|
/* */
|
|
/***************************************************************/
|
|
void print_expr_nodes_stats(void)
|
|
{
|
|
fprintf(ErrFp, " Expression nodes allocated: %d\n", ExprNodesAllocated);
|
|
fprintf(ErrFp, "Expression nodes high-water: %d\n", ExprNodesHighWater);
|
|
fprintf(ErrFp, " Expression nodes leaked: %d\n", ExprNodesUsed);
|
|
fprintf(ErrFp, " Parse level high-water: %d\n", parse_level_high_water);
|
|
}
|
|
|
|
/* Return 1 if a value is "true" for its type, 0 if "false" */
|
|
int truthy(Value const *v)
|
|
{
|
|
if (v->type == STR_TYPE) {
|
|
if (v->v.str && *(v->v.str)) {
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return (v->v.val != 0);
|
|
}
|