diff --git a/src/custom.h b/src/custom.h index 51429336..ac2fb7de 100644 --- a/src/custom.h +++ b/src/custom.h @@ -166,6 +166,10 @@ #define PASSTHRU_LEN 32 +#define MAX_RECURSION_LEVEL 1000 + +#define MAX_FUNC_ARGS 64 + #define PSBEGIN "# rem2ps begin" #define PSEND "# rem2ps end" diff --git a/src/custom.h.in b/src/custom.h.in index 51429336..ac2fb7de 100644 --- a/src/custom.h.in +++ b/src/custom.h.in @@ -166,6 +166,10 @@ #define PASSTHRU_LEN 32 +#define MAX_RECURSION_LEVEL 1000 + +#define MAX_FUNC_ARGS 64 + #define PSBEGIN "# rem2ps begin" #define PSEND "# rem2ps end" diff --git a/src/dorem.c b/src/dorem.c index 253086f3..f5b6e0d2 100644 --- a/src/dorem.c +++ b/src/dorem.c @@ -1268,20 +1268,28 @@ int DoSatRemind(Trigger *trig, TimeTrig *tt, ParsePtr p) { int iter, dse, r, start; Value v; - char const *s; - char const *t; + expr_node *sat_node; + int nonconst = 0; - t = p->pos; + sat_node = ParseExpr(p, &r); + if (r != OK) { + return r; + } + if (!sat_node) { + return E_SWERR; + } iter = 0; start = trig->scanfrom; while (iter++ < MaxSatIter) { dse = ComputeTriggerNoAdjustDuration(start, trig, tt, &r, 1, 0); if (r) { + free_expr_tree(sat_node); if (r == E_CANT_TRIG) return OK; else return r; } if (dse != start && trig->duration_days) { dse = ComputeTriggerNoAdjustDuration(start, trig, tt, &r, 1, trig->duration_days); if (r) { + free_expr_tree(sat_node); if (r == E_CANT_TRIG) return OK; else return r; } } else if (dse == start) { @@ -1294,13 +1302,18 @@ int DoSatRemind(Trigger *trig, TimeTrig *tt, ParsePtr p) SaveAllTriggerInfo(trig, tt, dse, tt->ttime, 1); } if (dse == -1) { + free_expr_tree(sat_node); return E_EXPIRED; } - s = p->pos; - r = EvaluateExpr(p, &v); - t = p->pos; - if (r) return r; - if (v.type != INT_TYPE && v.type != STR_TYPE) return E_BAD_TYPE; + r = evaluate_expr_node(sat_node, NULL, &v, &nonconst); + if (r) { + free_expr_tree(sat_node); + return r; + } + if (v.type != INT_TYPE && v.type != STR_TYPE) { + free_expr_tree(sat_node); + return E_BAD_TYPE; + } if ((v.type == INT_TYPE && v.v.val) || (v.type == STR_TYPE && *v.v.str)) { AdjustTriggerForDuration(trig->scanfrom, dse, trig, tt, 1); @@ -1325,17 +1338,17 @@ int DoSatRemind(Trigger *trig, TimeTrig *tt, ParsePtr p) } fprintf(ErrFp, "\n"); } + free_expr_tree(sat_node); return OK; } - p->pos = s; if (dse+trig->duration_days < start) { start++; } else { start = dse+trig->duration_days+1; } } - p->pos = t; LastTrigValid = 0; + free_expr_tree(sat_node); return E_CANT_TRIG; } diff --git a/src/dosubst.c b/src/dosubst.c index 4fc96685..32a818cd 100644 --- a/src/dosubst.c +++ b/src/dosubst.c @@ -12,6 +12,7 @@ /***************************************************************/ #include "config.h" +#include "types.h" #include "expr.h" #define L_IN_DOSUBST #include @@ -20,7 +21,6 @@ #include -#include "types.h" #include "globals.h" #include "err.h" #include "protos.h" diff --git a/src/dynbuf.c b/src/dynbuf.c index 8aec1485..9478aae9 100644 --- a/src/dynbuf.c +++ b/src/dynbuf.c @@ -124,7 +124,9 @@ int DBufPuts(DynamicBuffer *dbuf, char const *str) **********************************************************************/ void DBufFree(DynamicBuffer *dbuf) { - if (dbuf->buffer != dbuf->staticBuf) free(dbuf->buffer); + if (dbuf->buffer != NULL && dbuf->buffer != dbuf->staticBuf) { + free(dbuf->buffer); + } DBufInit(dbuf); } diff --git a/src/err.h b/src/err.h index f957d2ee..54349e52 100644 --- a/src/err.h +++ b/src/err.h @@ -184,7 +184,7 @@ EXTERN char *ErrMsg[] "RUN disabled", "Domain error", "Invalid identifier", - "Recursive function call detected", + "Too many recursive function calls", "", "Cannot modify system variable", "C library function can't represent date/time", diff --git a/src/expr.c b/src/expr.c index e812dfd0..36199015 100644 --- a/src/expr.c +++ b/src/expr.c @@ -2,170 +2,1060 @@ /* */ /* EXPR.C */ /* */ -/* This file contains routines to parse and evaluate */ -/* expressions. */ +/* Remind's expression-evaluation engine */ /* */ -/* Copyright 1992-2024 by Dianne Skoll */ +/* 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-2024 by Dianne Skoll */ /* SPDX-License-Identifier: GPL-2.0-only */ /* */ /***************************************************************/ - #include "config.h" -#include -#include -#include -#include - -#include - #include "err.h" #include "types.h" -#include "expr.h" #include "protos.h" #include "globals.h" +#include "expr.h" +#include +#include +#include +/* Constants for the "how" arg to compare() */ +enum { EQ, GT, LT, GE, LE, NE }; + +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) == '_') -#define EQ 0 -#define GT 1 -#define LT 2 -#define GE 3 -#define LE 4 -#define NE 5 -extern int NumFuncs; +#define DBG(x) do { if (DebugFlag & DB_PRTEXPR) { x; } } while(0) -static int Multiply(void), Divide(void), Mod(void), Add(void), - Subtract(void), GreaterThan(void), LessThan(void), - EqualTo(void), NotEqual(void), LessOrEqual(void), - GreaterOrEqual(void), LogAND(void), LogOR(void), - UnMinus(void), LogNot(void), - Compare(int); - -static int MakeValue (char const *s, Value *v, Var *locals, ParsePtr p); - -/* Binary operators - all left-associative */ - -/* Operator precedence: - * Highest: Unary - Unary ! - * / % - * + - - * < <= > >= - * == != - * && - * Lowest: || - * - */ - -/* Make SURE they are sorted lexically... this may die on an EBCDIC - system... */ - -Operator BinOp[] = { - { "!=", 15, BIN_OP, NotEqual }, - { "%", 20, BIN_OP, Mod }, - { "&&", 14, BIN_OP, LogAND }, - { "*", 20, BIN_OP, Multiply }, - { "+", 18, BIN_OP, Add }, - { "-", 18, BIN_OP, Subtract }, - { "/", 20, BIN_OP, Divide }, - { "<", 16, BIN_OP, LessThan }, - { "<=", 16, BIN_OP, LessOrEqual }, - { "==", 15, BIN_OP, EqualTo }, - { ">", 16, BIN_OP, GreaterThan }, - { ">=", 16, BIN_OP, GreaterOrEqual }, - { "||", 12, BIN_OP, LogOR }, -}; -#define NUM_BIN_OPS (sizeof(BinOp) / sizeof(Operator)) - -/* These ones must be sorted too. */ -Operator UnOp[] = { - { "!", 22, UN_OP, LogNot }, - { "-", 22, UN_OP, UnMinus }, -}; -#define NUM_UN_OPS (sizeof(UnOp) / sizeof(Operator)) +#define PEEK_TOKEN() peek_expr_token(&ExprBuf, *e) +#define GET_TOKEN() parse_expr_token(&ExprBuf, e) extern BuiltinFunc Func[]; +extern int NumFuncs; -static Operator OpStack[OP_STACK_SIZE]; -static int OpStackPtr = 0; -static int OpStackHiWater = 0; -/* ValStack can't be static - needed by funcs.c */ -Value ValStack[VAL_STACK_SIZE]; -int ValStackPtr = 0; -int ValStackHiWater = 0; +static int ExprNodesAllocated = 0; +static int ExprNodesHighWater = 0; +static int ExprNodesUsed = 0; -/***************************************************************/ -/* */ -/* DebugPerform */ -/* */ -/* Execute an operator or function with debugging. */ -/* */ -/***************************************************************/ -static int DebugPerform(Operator *op) +/* Forward references */ +static expr_node * parse_expression_aux(char const **e, int *r, Var *locals); +static char const *get_operator_name(expr_node *node); + +/* This is super-skanky... we keep track of the currently-executing + user-defined function in a global var */ +static UserFunc *CurrentUserFunc = NULL; + +#define ALLOC_CHUNK 64 +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 ExprNodesHighWater) ExprNodesHighWater = ExprNodesUsed; + node = expr_node_free_list; + expr_node_free_list = node->child; + node->type = N_FREE; + node->child = NULL; + node->sibling = NULL; + node->num_kids = 0; + return node; +} + +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; +} + +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", ErrMsg[r]); + } else { + PrintValue(ans, ErrFp); + fprintf(ErrFp, "\n"); + } + va_end(argptr); +} + +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", ErrMsg[r]); + } else { + PrintValue(ans, ErrFp); + fprintf(ErrFp, "\n"); + } + va_end(argptr); +} + +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", ErrMsg[r]); + } else { + PrintValue(ans, ErrFp); + fprintf(ErrFp, "\n"); + } + va_end(argptr); +} + +static int +get_var(expr_node *node, Value *ans) +{ + if (node->type == N_SHORT_VAR) { + return GetVarValue(node->u.name, ans); + } else { + return GetVarValue(node->u.value.v.str, ans); + } +} + +static int +get_sysvar(expr_node *node, Value *ans) +{ + if (node->type == N_SHORT_SYSVAR) { + return GetSysVar(node->u.name, ans); + } else { + return GetSysVar(node->u.value.v.str, ans); + } +} + +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; + + /* Check that we have the right number of argumens */ + if (node->num_kids < f->minargs) return E_2FEW_ARGS; + if (node->num_kids > f->maxargs && f->maxargs != NO_MAX) return E_2MANY_ARGS; + if (f->newfunc) { + return node->u.builtin_func->newfunc(node, locals, ans, nonconst); + } + + /* Build up the old-style stack frame */ + info.nargs = node->num_kids; + + if (info.nargs) { + info.args = malloc(info.nargs * sizeof(Value)); + if (!info.args) { + return E_NO_MEM; + } + } else { + info.args = NULL; + } + + kid = node->child; + i = 0; + while (kid) { + r = evaluate_expr_node(kid, locals, &(info.args[i]), nonconst); + if (r != OK) { + for (j=0; jsibling; + } + + /* Actually call the function */ + if (DebugFlag & DB_PRTEXPR) { + fprintf(ErrFp, "%s(", f->name); + for (i=0; i 0) { + fprintf(ErrFp, " "); + } + PrintValue(&(info.args[i]), ErrFp); + if (i < info.nargs-1) { + fprintf(ErrFp, ","); + } + } + fprintf(ErrFp, ") => "); + } + r = f->func(&info); + if (r == OK) { + r = CopyValue(ans, &info.retval); + } + + /* Debug */ + if (DebugFlag & DB_PRTEXPR) { + if (r) { + fprintf(ErrFp, "%s", ErrMsg[r]); + } else { + PrintValue(ans, ErrFp); + } + fprintf(ErrFp, "\n"); + } + if (r != OK) { + Eprint("%s(): %s", f->name, ErrMsg[r]); + } + /* Clean up */ + if (info.args) { + for (i=0; itype == N_SHORT_USER_FUNC) { + fname = node->u.name; + } else { + fname = node->u.value.v.str; + } + fprintf(ErrFp, "%s %s(", ErrMsg[E_ENTER_FUN], fname); + for (i=0; itype == N_SHORT_USER_FUNC) { + fname = node->u.name; + } else { + fname = node->u.value.v.str; + } + fprintf(ErrFp, "%s %s(", ErrMsg[E_LEAVE_FUN], fname); + for (i=0; i "); + if (r == OK) { + PrintValue(ans, ErrFp); + } else { + fprintf(ErrFp, "%s", ErrMsg[r]); + } + fprintf(ErrFp, "\n"); +} + +static int +eval_userfunc(expr_node *node, Value *locals, Value *ans, int *nonconst) +{ + UserFunc *f; + UserFunc *previously_executing; + + char const *fname; + if (node->type == N_SHORT_USER_FUNC) { + fname = node->u.name; + } else { + fname = node->u.value.v.str; + } + f = FindUserFunc(fname); + Value *new_locals = NULL; + expr_node *kid; + int i, r, j, pushed; + + if (!f) { + Eprint("%s: `%s'", ErrMsg[E_UNDEF_FUNC], fname); + return E_UNDEF_FUNC; + } + if (node->num_kids < f->nargs) { + DBG(fprintf(ErrFp, "%s(...) => %s\n", fname, ErrMsg[E_2FEW_ARGS])); + return E_2FEW_ARGS; + } + if (node->num_kids > f->nargs) { + DBG(fprintf(ErrFp, "%s(...) => %s\n", fname, ErrMsg[E_2MANY_ARGS])); + return E_2MANY_ARGS; + } + + /* Build up the array of locals */ + if (node->num_kids) { + new_locals = malloc(node->num_kids * sizeof(Value)); + if (!new_locals) { + DBG(fprintf(ErrFp, "%s(...) => %s\n", fname, ErrMsg[E_NO_MEM])); + return E_NO_MEM; + } + kid = node->child; + i = 0; + while(kid) { + r = evaluate_expr_node(kid, locals, &(new_locals[i]), nonconst); + if (r != OK) { + for (j=0; jsibling; + } + } + + if (FuncRecursionLevel >= MAX_RECURSION_LEVEL) { + return E_RECURSIVE; + } + + previously_executing = CurrentUserFunc; + CurrentUserFunc = f; + FuncRecursionLevel++; + pushed = push_call(f->filename, f->name, f->lineno); + if (DebugFlag & DB_PRTEXPR) { + debug_enter_userfunc(node, new_locals, f->nargs); + } + + r = evaluate_expr_node(f->node, new_locals, ans, nonconst); + + if (DebugFlag & DB_PRTEXPR) { + debug_exit_userfunc(node, ans, r, new_locals, f->nargs); + } + if (r != OK) { + Eprint("%s", ErrMsg[r]); + } + if (pushed == OK) pop_call(); + FuncRecursionLevel--; + CurrentUserFunc = previously_executing; + + /* Clean up */ + for (j=0; jnum_kids; j++) { + DestroyValue(new_locals[j]); + } + if (new_locals) { + free(new_locals); + } + return r; +} + +int +evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst) { int r; - if (op->type == UN_OP) { - fprintf(ErrFp, "%s ", op->name); - PrintValue(&ValStack[ValStackPtr-1], ErrFp); - } else { /* Must be binary operator */ - PrintValue(&ValStack[ValStackPtr-2], ErrFp); - fprintf(ErrFp, " %s ", op->name); - PrintValue(&ValStack[ValStackPtr-1], ErrFp); + if (!node) { + return E_SWERR; + } + switch(node->type) { + case N_FREE: + case N_ERROR: + ans->type = ERR_TYPE; + return E_SWERR; + case N_CONSTANT: + return CopyValue(ans, &(node->u.value)); + case N_SHORT_VAR: + *nonconst = 1; + r = get_var(node, ans); + DBG(debug_evaluation(ans, r, "%s", node->u.name)); + return r; + case N_VARIABLE: + *nonconst = 1; + r = get_var(node, ans); + DBG(debug_evaluation(ans, r, "%s", node->u.value.v.str)); + return r; + case N_LOCAL_VAR: + r = CopyValue(ans, &(locals[node->u.arg])); + DBG(debug_evaluation(ans, r, "%s", CurrentUserFunc->args[node->u.arg])); + return r; + case N_SHORT_SYSVAR: + *nonconst = 1; + r = get_sysvar(node, ans); + DBG(debug_evaluation(ans, r, "$%s", node->u.name)); + return r; + case N_SYSVAR: + *nonconst = 1; + r = get_sysvar(node, ans); + DBG(debug_evaluation(ans, r, "$%s", node->u.value.v.str)); + return r; + case N_BUILTIN_FUNC: + if (!node->u.builtin_func->is_constant) { + *nonconst = 1; + } + return eval_builtin(node, locals, ans, nonconst); + case N_USER_FUNC: + case N_SHORT_USER_FUNC: + return eval_userfunc(node, locals, ans, nonconst); + case N_BINARY_OPERATOR: + case N_UNARY_OPERATOR: + r = node->u.operator_func(node, locals, ans, nonconst); + if (r != OK) { + Eprint("`%s': %s", get_operator_name(node), ErrMsg[r]); + } + return r; + } + return E_SWERR; +} + +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 "???"; + } +} + +/* + * All of the functions that implement operators + */ +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; } - r = (op->func)(); - fprintf(ErrFp, " => "); - if (!r) { - PrintValue(&ValStack[ValStackPtr-1], ErrFp); - putc('\n', ErrFp); - } else { - fprintf(ErrFp, "%s\n", ErrMsg[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; +} + +static int compare_eq(expr_node *node, Value *locals, Value *ans, int *nonconst) { + return compare(node, locals, ans, nonconst, EQ); +} +static int compare_ne(expr_node *node, Value *locals, Value *ans, int *nonconst) { + return compare(node, locals, ans, nonconst, NE); +} +static int compare_le(expr_node *node, Value *locals, Value *ans, int *nonconst) { + return compare(node, locals, ans, nonconst, LE); +} +static int compare_ge(expr_node *node, Value *locals, Value *ans, int *nonconst) { + return compare(node, locals, ans, nonconst, GE); +} +static int compare_lt(expr_node *node, Value *locals, Value *ans, int *nonconst) { + return compare(node, locals, ans, nonconst, LT); +} +static int compare_gt(expr_node *node, Value *locals, Value *ans, int *nonconst) { + return compare(node, locals, ans, nonconst, GT); +} + + +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 fode 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, "+")); + 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; +} + +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; +} + +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; ichild, 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, "/")); + 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, "/")); + return E_2HIGH; + } + *ans = v1; + ans->v.val /= v2.v.val; + DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "/")); + return OK; + } + DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "/")); + DestroyValue(v1); + DestroyValue(v2); + return E_BAD_TYPE; +} + +static int +do_mod(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 (v1.type == INT_TYPE && v2.type == INT_TYPE) { + if (v2.v.val == 0) { + DBG(debug_evaluation_binop(ans, E_DIV_ZERO, &v1, &v2, "%%")); + 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, "%%")); + return E_2HIGH; + } + *ans = v1; + ans->v.val %= v2.v.val; + DBG(debug_evaluation_binop(ans, OK, &v1, &v2, "%%")); + return OK; + } + + DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v1, &v2, "%%")); + DestroyValue(v1); + DestroyValue(v2); + return E_BAD_TYPE; +} + +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; + if (v1.type != INT_TYPE) { + DBG(debug_evaluation_unop(ans, E_BAD_TYPE, &v1, "!")); + DestroyValue(v1); + return E_BAD_TYPE; + } + ans->type = INT_TYPE; + ans->v.val = !(v1.v.val); + DBG(debug_evaluation_unop(ans, OK, &v1, "!")); + return OK; +} + +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; + } + ans->type = INT_TYPE; + ans->v.val = -(v1.v.val); + DBG(debug_evaluation_unop(ans, OK, &v1, "-")); + return OK; +} + +static int +logical_or(expr_node *node, Value *locals, Value *ans, int *nonconst) +{ + Value v; + int r = evaluate_expr_node(node->child, locals, &v, nonconst); + if (r != OK) return r; + + if (v.type == STR_TYPE) { + DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v, NULL, "||")); + DestroyValue(v); + return E_BAD_TYPE; + } + if (v.v.val) { + *ans = v; + DBG(debug_evaluation_binop(ans, OK, &v, NULL, "||")); + return OK; + } + r = evaluate_expr_node(node->child->sibling, locals, ans, nonconst); + if (r == OK && ans->type == STR_TYPE) { + DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v, ans, "||")); + DestroyValue(*ans); + return E_BAD_TYPE; + } + DBG(debug_evaluation_binop(ans, r, &v, ans, "||")); + return r; +} + +static int +logical_and(expr_node *node, Value *locals, Value *ans, int *nonconst) +{ + Value v; + int r = evaluate_expr_node(node->child, locals, &v, nonconst); + if (r != OK) return r; + + if (v.type == STR_TYPE) { + DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v, NULL, "&&")); + DestroyValue(v); + return E_BAD_TYPE; + } + if (!v.v.val) { + ans->type = v.type; + ans->v.val = 0; + DBG(debug_evaluation_binop(ans, OK, &v, NULL, "&&")); + return OK; + } + r = evaluate_expr_node(node->child->sibling, locals, ans, nonconst); + if (r == OK && ans->type == STR_TYPE) { + DBG(debug_evaluation_binop(ans, E_BAD_TYPE, &v, NULL, "&&")); + DestroyValue(*ans); + return E_BAD_TYPE; + } + DBG(debug_evaluation_binop(ans, r, &v, ans, "&&")); return r; } /***************************************************************/ /* */ -/* CleanStack */ -/* */ -/* Clean the stack after an error occurs. */ -/* */ -/***************************************************************/ -static void CleanStack(int old_op_stack_ptr, int old_val_stack_ptr) -{ - int i; - - for (i=old_val_stack_ptr; iis_constant && (p != NULL)) p->nonconst_expr = 1; - r = CallFunc(f, 0); - } else { - r = CallUserFunc(ufname, 0, p); - free((char *) ufname); - } - if (r) return r; - r = ParseExprToken(&ExprBuf, s); /* Guaranteed to be right paren. */ - if (r) return r; - } else { /* Function has some arguments */ - while(1) { - args++; - r = Evaluate(s, locals, p); - if (r) { - if (!f) free((char *) ufname); - return r; - } - if (*DBufValue(&ExprBuf) == ')') break; - else if (*DBufValue(&ExprBuf) != ',') { - if (!f) free((char *) ufname); - Eprint("%s: `%c'", ErrMsg[E_EXPECT_COMMA], - *DBufValue(&ExprBuf)); - DBufFree(&ExprBuf); - return E_EXPECT_COMMA; - } - } - if (f) { - if (!f->is_constant && (p != NULL)) p->nonconst_expr = 1; - r = CallFunc(f, args); - } else { - r = CallUserFunc(ufname, args, p); - free((char *) ufname); - } - DBufFree(&ExprBuf); - if (r) return r; - } - } else { /* Unary operator */ - o = FindOperator(DBufValue(&ExprBuf), UnOp, NUM_UN_OPS); - if (o) { - DBufFree(&ExprBuf); - PushOpStack(*o); - continue; /* Still looking for an atomic vlue */ - } else if (!ISID(*DBufValue(&ExprBuf)) && - *DBufValue(&ExprBuf) != '$' && - *DBufValue(&ExprBuf) != '"' && - *DBufValue(&ExprBuf) != '\'') { - Eprint("%s `%c'", ErrMsg[E_ILLEGAL_CHAR], - *DBufValue(&ExprBuf)); - DBufFree(&ExprBuf); - return E_ILLEGAL_CHAR; - } else { /* Must be a literal value */ - r = MakeValue(DBufValue(&ExprBuf), &va, locals, p); - DBufFree(&ExprBuf); - if (r) return r; - PushValStack(va); - } - } -/* OK, we've got a literal value; now, we're looking for the end of the - expression, or a binary operator. */ - r = ParseExprToken(&ExprBuf, s); - if (r) return r; - if (*DBufValue(&ExprBuf) == 0 || - *DBufValue(&ExprBuf) == ',' || - *DBufValue(&ExprBuf) == ']' || - *DBufValue(&ExprBuf) == ')') { - /* We've hit the end of the expression. Pop off and evaluate until - OpStackPtr = OpBase and ValStackPtr = ValBase+1 */ - while (OpStackPtr > OpBase) { - PopOpStack(op); - if (DebugFlag & DB_PRTEXPR) - r=DebugPerform(&op); - else - r=(op.func)(); - if (r) { - DBufFree(&ExprBuf); - Eprint("`%s': %s", op.name, ErrMsg[r]); - return r; - } - } - if (ValStackPtr != ValBase+1) { - DBufFree(&ExprBuf); - return E_STACK_ERR; - } - return OK; - } - /* Must be a binary operator */ - o = FindOperator(DBufValue(&ExprBuf), BinOp, NUM_BIN_OPS); - DBufFree(&ExprBuf); - if (!o) return E_EXPECTING_BINOP; - - /* While operators of higher or equal precedence are on the stack, - pop them off and evaluate */ - while (OpStackPtr > OpBase && OpStack[OpStackPtr-1].prec >= o->prec) { - PopOpStack(op2); - if (r) return r; - if (DebugFlag & DB_PRTEXPR) - r=DebugPerform(&op2); - else - r=(op2.func)(); - if (r) { - Eprint("`%s': %s", op2.name, ErrMsg[r]); - return r; - } - } - PushOpStack(*o); + if (node) { + 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; } -/***************************************************************/ -/* */ -/* MakeValue */ -/* Generate a literal value. It's either a string, a number, */ -/* a date or the value of a symbol. */ -/* */ -/***************************************************************/ -static int MakeValue(char const *s, Value *v, Var *locals, ParsePtr p) +static int set_long_name(expr_node *node, char const *s) { - int len; - int h, m, r; - int ampm = 0; - int prev_val; + 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; +} +static expr_node * +parse_function_call(char const **e, int *r, Var *locals) +{ + expr_node *node = alloc_expr_node(r); + expr_node *arg; + char *s; + + 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); + } else { + if (set_long_name(node, s) != OK) { + *r = E_NO_MEM; + return free_expr_tree(node); + } + 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); + 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 `)'", ErrMsg[E_ILLEGAL_CHAR]); + *r = E_ILLEGAL_CHAR; + return free_expr_tree(node); + } + } + } + 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) *r = E_2FEW_ARGS; + if (node->num_kids > f->maxargs && f->maxargs != NO_MAX) *r = E_2MANY_ARGS; + } + if (*r != OK) { + if (node->type == N_BUILTIN_FUNC) { + f = node->u.builtin_func; + Eprint("%s: %s", f->name, ErrMsg[*r]); + } + return free_expr_tree(node); + } + return node; +} + +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; + + ampm = 0; if (*s == '\"') { /* It's a literal string "*/ len = strlen(s)-1; - v->type = STR_TYPE; - v->v.str = malloc(len); - if (! v->v.str) { - v->type = ERR_TYPE; + 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(v->v.str, s+1, len-1); - *(v->v.str+len-1) = 0; + 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=ParseLiteralDate(&s, &h, &m))) return r; + if ((r=ParseLiteralDate(&s, &dse, &tim)) != 0) return r; if (*s != '\'') return E_BAD_DATE; - if (m == NO_TIME) { - v->type = DATE_TYPE; - v->v.val = h; + if (tim == NO_TIME) { + atom->u.value.type = DATE_TYPE; + atom->u.value.v.val = dse; } else { - v->type = DATETIME_TYPE; - v->v.val = (h * MINUTES_PER_DAY) + m; + 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 - use len to hold it.*/ - len = 0; + } 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)) { - len *= 10; - len += (*s++ - '0'); - if (len < prev_val) { + val *= 10; + val += (*s++ - '0'); + if (val < prev_val) { /* We overflowed */ return E_2HIGH; } - prev_val = len; + prev_val = val; } if (*s == ':' || *s == '.' || *s == TimeSep) { /* Must be a literal time */ s++; if (!isdigit(*s)) return E_BAD_TIME; - h = len; + h = val; m = 0; while (isdigit(*s)) { m *= 10; @@ -585,40 +1441,858 @@ static int MakeValue(char const *s, Value *v, Var *locals, ParsePtr p) } } } - v->type = TIME_TYPE; - v->v.val = h*60 + m; + 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) return E_BAD_NUMBER; - v->type = INT_TYPE; - v->v.val = len; + atom->u.value.type = INT_TYPE; + atom->u.value.v.val = val; return OK; - } else if (*s == '$') { /* A system variable */ - if (p) p->nonconst_expr = 1; - if (DebugFlag & DB_PRTEXPR) - fprintf(ErrFp, "%s => ", s); - r = GetSysVar(s+1, v); - - if (! (DebugFlag & DB_PRTEXPR)) return r; - if (r == OK) { - PrintValue(v, ErrFp); - putc('\n', ErrFp); - } - return r; - } else { /* Must be a symbol */ - if (DebugFlag & DB_PRTEXPR) - fprintf(ErrFp, "%s => ", s); } - r = GetVarValue(s, v, locals, p); - if (! (DebugFlag & DB_PRTEXPR)) return r; + atom->u.value.type = ERR_TYPE; + Eprint("`%s': %s", DBufValue(&ExprBuf), ErrMsg[E_ILLEGAL_CHAR]); + return E_ILLEGAL_CHAR; +} + +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->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", s, ErrMsg[E_NOSUCH_VAR]); + 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) { - PrintValue(v, ErrFp); - putc('\n', ErrFp); + atom->type = N_CONSTANT; + } else { + atom->type = N_ERROR; } return r; } +static expr_node * +parse_atom(char const **e, int *r, Var *locals) +{ + expr_node *node; + char const *s; + *r = PEEK_TOKEN(); + if (*r != OK) return NULL; + + /* Ignore unary-plus operators */ + while (TOKEN_IS("+")) { + *r = GET_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); + 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 (!ISID(*s) && + *s != '%' && + *s != '$' && + *s != '"' && + *s != '\'') { + Eprint("%s `%c'", ErrMsg[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); + } + + /* It's a constant or a variable reference */ + *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) { + return free_expr_tree(node); + } + return node; +} + +/* + * 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 + */ + +/* + * FACTOR_EXP: '-' FACTOR_EXP | + * '!' FACTOR_EXP | + * ATOM + */ +static expr_node * +parse_factor(char const **e, int *r, Var *locals) +{ + expr_node *node; + expr_node *factor_node; + char op; + *r = PEEK_TOKEN(); + if (*r != OK) { + return NULL; + } + if (TOKEN_IS("!") || TOKEN_IS("-")) { + if (TOKEN_IS("!")) { + op = '!'; + } else { + op = '-'; + } + factor_node = alloc_expr_node(r); + if (!factor_node) { + return NULL; + } + factor_node->type = N_UNARY_OPERATOR; + if (TOKEN_IS("!")) { + factor_node->u.operator_func = logical_not; + } else { + factor_node->u.operator_func = unary_minus; + } + /* Pull off the peeked token */ + GET_TOKEN(); + node = parse_factor(e, r, locals); + if (*r != OK) { + return free_expr_tree(factor_node); + } + add_child(factor_node, node); + *r = PEEK_TOKEN(); + if (*r != OK) { + return free_expr_tree(factor_node); + } + if (factor_node->child->type == N_CONSTANT && + factor_node->child->u.value.type == INT_TYPE) { + if (op == '-') { + factor_node->child->u.value.v.val = -factor_node->child->u.value.v.val; + } else { + factor_node->child->u.value.v.val = !factor_node->child->u.value.v.val; + } + node = factor_node->child; + factor_node->child = NULL; + free_expr_tree(factor_node); + return node; + } + /* TODO: Optimize (- n) to -n when n is an int constant */ + return factor_node; + } + return parse_atom(e, r, locals); +} + +/* + * 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) +{ + expr_node *node; + expr_node *term_node; + + node = parse_factor(e, r, locals); + 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_BINARY_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); + 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; +} + +/* + * 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) +{ + expr_node *node; + expr_node *cmp_node; + + node = parse_term_expr(e, r, locals); + 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_BINARY_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); + if (*r != OK) { + return free_expr_tree(cmp_node); + } + add_child(cmp_node, node); + node = cmp_node; + } + return node; +} + +/* + * 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) +{ + expr_node *node; + expr_node *eq_node; + + node = parse_cmp_expr(e, r, locals); + 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_BINARY_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); + if (*r != OK) { + free_expr_tree(eq_node); + return free_expr_tree(node); + } + add_child(eq_node, node); + node = eq_node; + } + return node; +} + +/* + * 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) +{ + expr_node *node; + expr_node *and_node; + + node = parse_eq_expr(e, r, locals); + 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_BINARY_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); + if (*r != OK) { + return free_expr_tree(and_node); + } + add_child(and_node, node); + node = and_node; + } + return node; +} + +static expr_node * +parse_or_expr(char const **e, int *r, Var *locals) +{ + expr_node *node; + expr_node *logand_node; + + node = parse_and_expr(e, r, locals); + + 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_BINARY_OPERATOR; + logand_node->u.operator_func = logical_and; + add_child(logand_node, node); + node = parse_and_expr(e, r, locals); + if (*r != OK) { + free_expr_tree(logand_node); + return free_expr_tree(node); + } + add_child(logand_node, node); + node = logand_node; + } + return node; +} + +static expr_node * +parse_expression_aux(char const **e, int *r, Var *locals) +{ + expr_node *node; + expr_node *logor_node; + node = parse_or_expr(e, r, locals); + + 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_BINARY_OPERATOR; + logor_node->u.operator_func = logical_or; + add_child(logor_node, node); + node = parse_or_expr(e, r, locals); + if (*r != OK) { + free_expr_tree(logor_node); + return free_expr_tree(node); + } + add_child(logor_node, node); + node = logor_node; + } + return node; +} + +expr_node * +parse_expression(char const **e, int *r, Var *locals) +{ + char const *orig = *e; + expr_node *node = parse_expression_aux(e, r, locals); + 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", ErrMsg[*r]); + } else { + fprintf(ErrFp, " => "); + print_expr_tree(node, ErrFp); + fprintf(ErrFp, "\n"); + } + if (**e && (**e != ']')) { + fprintf(ErrFp, " Unparsed: %s\n", *e); + } + } + return node; +} + + +/* Debugging routines */ +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; + } +} + +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_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, "(B:%s", node->u.builtin_func->name); + if (node->child) fprintf(fp, " "); + print_kids(node, fp); + fprintf(fp, ")"); + return; + case N_SHORT_USER_FUNC: + fprintf(fp, "(U:%s", node->u.name); + if (node->child) fprintf(fp, " "); + print_kids(node, fp); + fprintf(fp, ")"); + return; + case N_USER_FUNC: + fprintf(fp, "(U:%s", node->u.value.v.str); + if (node->child) fprintf(fp, " "); + print_kids(node, fp); + fprintf(fp, ")"); + return; + case N_BINARY_OPERATOR: + case N_UNARY_OPERATOR: + fprintf(fp, "(%s ", get_operator_name(node)); + print_kids(node, fp); + fprintf(fp, ")"); + return; + default: + return; + } +} + +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 */ +/* 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_expr_node(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; ytype == 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. */ +/* */ +/***************************************************************/ +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; +} + +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; +} + +/***************************************************************/ +/* */ +/* ParseLiteralDate */ +/* */ +/* Parse a literal date or datetime. Return result in dse */ +/* and tim; update s. */ +/* */ +/***************************************************************/ +int ParseLiteralDate(char const **s, int *dse, int *tim) +{ + int y, m, d; + int r; + + y=0; m=0; d=0; + + *tim = NO_TIME; + if (!isdigit(**s)) return E_BAD_DATE; + while (isdigit(**s)) { + y *= 10; + y += *(*s)++ - '0'; + } + 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. */ @@ -767,730 +2441,10 @@ int DoCoerce(char type, Value *v) } } -/***************************************************************/ -/* */ -/* Add */ -/* */ -/* Perform addition. */ -/* */ -/***************************************************************/ -static int Add(void) +void +print_expr_nodes_stats(void) { - Value v1, v2, v3; - int r; - - size_t l1, l2; - - PopValStack(v2); - if ( (r = FnPopValStack(&v1)) ) { - DestroyValue(v2); - 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)) { - return E_2HIGH; - } - v1.v.val += v2.v.val; - PushValStack(v1); - 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)) return E_DATE_OVER; - v1.v.val += v2.v.val; - if (v1.v.val < 0) return E_DATE_OVER; - v1.type = DATE_TYPE; - PushValStack(v1); - 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)) return E_DATE_OVER; - v1.v.val += v2.v.val; - if (v1.v.val < 0) return E_DATE_OVER; - v1.type = DATETIME_TYPE; - PushValStack(v1); - 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)) return E_DATE_OVER; - v1.v.val += v2.v.val; - v1.v.val = v1.v.val % MINUTES_PER_DAY; - if (v1.v.val < 0) v1.v.val += MINUTES_PER_DAY; - v1.type = TIME_TYPE; - PushValStack(v1); - return OK; - } - -/* If either is a string, coerce them both to strings and concatenate */ - if (v1.type == STR_TYPE || v2.type == STR_TYPE) { - if ( (r = DoCoerce(STR_TYPE, &v1)) ) { - DestroyValue(v1); DestroyValue(v2); - return r; - } - if ( (r = DoCoerce(STR_TYPE, &v2)) ) { - DestroyValue(v1); DestroyValue(v2); - return r; - } - v3.type = STR_TYPE; - l1 = strlen(v1.v.str); - l2 = strlen(v2.v.str); - if (MaxStringLen > 0 && (l1 + l2 > (size_t) MaxStringLen)) { - DestroyValue(v1); DestroyValue(v2); - return E_STRING_TOO_LONG; - } - v3.v.str = malloc(l1 + l2 + 1); - if (!v3.v.str) { - DestroyValue(v1); DestroyValue(v2); - return E_NO_MEM; - } - strcpy(v3.v.str, v1.v.str); - strcpy(v3.v.str+l1, v2.v.str); - DestroyValue(v1); DestroyValue(v2); - PushValStack(v3); - return OK; - } - - /* Don't handle other types yet */ - return E_BAD_TYPE; -} - -/***************************************************************/ -/* */ -/* Subtract */ -/* */ -/* Perform subtraction. */ -/* */ -/***************************************************************/ -static int Subtract(void) -{ - Value v1, v2; - int r; - - PopValStack(v2); - if ( (r = FnPopValStack(&v1)) ) { - DestroyValue(v2); - 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)) return E_2HIGH; - v1.v.val -= v2.v.val; - PushValStack(v1); - 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)) return E_DATE_OVER; - v1.v.val -= v2.v.val; - if (v1.v.val < 0) return E_DATE_OVER; - PushValStack(v1); - 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)) return E_DATE_OVER; - v1.v.val -= v2.v.val; - if (v1.v.val < 0) return E_DATE_OVER; - PushValStack(v1); - 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) { - v1.v.val = (v1.v.val - v2.v.val) % MINUTES_PER_DAY; - if (v1.v.val < 0) v1.v.val += MINUTES_PER_DAY; - PushValStack(v1); - 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)) return E_DATE_OVER; - v1.v.val -= v2.v.val; - v1.type = INT_TYPE; - PushValStack(v1); - return OK; - } - - /* Must be types illegal for subtraction */ - DestroyValue(v1); DestroyValue(v2); - return E_BAD_TYPE; -} - -/***************************************************************/ -/* */ -/* Multiply */ -/* */ -/* Perform multiplication. */ -/* */ -/***************************************************************/ -static int Multiply(void) -{ - Value v1, v2, v3; - char *ptr; - int r; - - PopValStack(v2); - if ( (r = FnPopValStack(&v1)) ) { - DestroyValue(v2); - 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)) { - return E_2HIGH; - } - if (_private_mul_overflow(v1.v.val, v2.v.val)) return E_2HIGH; - v1.v.val *= v2.v.val; - PushValStack(v1); - 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) { - return E_2LOW; - } - if (rep == 0 || !str || !*str) { - /* Empty string */ - DestroyValue(v1); DestroyValue(v2); - v3.type = STR_TYPE; - v3.v.str = malloc(1); - if (!v3.v.str) { - return E_NO_MEM; - } - *v3.v.str = 0; - PushValStack(v3); - return OK; - } - - /* Create the new value */ - l = (int) strlen(str); - if (l * rep < 0) { - DestroyValue(v1); DestroyValue(v2); - return E_STRING_TOO_LONG; - } - if ((unsigned long) l * (unsigned long) rep >= (unsigned long) INT_MAX) { - DestroyValue(v1); DestroyValue(v2); - return E_STRING_TOO_LONG; - } - if (MaxStringLen > 0 && ((unsigned long) l * (unsigned long) rep) > (unsigned long)MaxStringLen) { - DestroyValue(v1); DestroyValue(v2); - return E_STRING_TOO_LONG; - } - v3.type = STR_TYPE; - v3.v.str = malloc(l * rep + 1); - if (!v3.v.str) { - DestroyValue(v1); DestroyValue(v2); - return E_NO_MEM; - } - *v3.v.str = 0; - ptr = v3.v.str; - for (int i=0; i 0); break; - case LE: v3.v.val = (strcmp(v1.v.str, v2.v.str) <= 0); break; - case GE: v3.v.val = (strcmp(v1.v.str, v2.v.str) >= 0); break; - } - } else { - switch(how) { - case EQ: v3.v.val = (v1.v.val == v2.v.val); break; - case NE: v3.v.val = (v1.v.val != v2.v.val); break; - case LT: v3.v.val = (v1.v.val < v2.v.val); break; - case GT: v3.v.val = (v1.v.val > v2.v.val); break; - case LE: v3.v.val = (v1.v.val <= v2.v.val); break; - case GE: v3.v.val = (v1.v.val >= v2.v.val); break; - } - } - DestroyValue(v1); DestroyValue(v2); - PushValStack(v3); - return OK; -} - -/***************************************************************/ -/* */ -/* LogOR */ -/* */ -/* Do logical OR */ -/* */ -/***************************************************************/ -static int LogOR(void) -{ - Value v1, v2; - int r; - - PopValStack(v2); - if ( (r = FnPopValStack(&v1)) ) { - DestroyValue(v2); - return r; - } - - if (v1.type != v2.type || v1.type == STR_TYPE || v2.type == STR_TYPE) { - DestroyValue(v1); DestroyValue(v2); - return E_BAD_TYPE; - } - - if (v1.v.val == 0) { - v1.v.val = v2.v.val; - } - PushValStack(v1); - return OK; -} - -/***************************************************************/ -/* */ -/* LogAND */ -/* */ -/* Do logical AND */ -/* */ -/***************************************************************/ -static int LogAND(void) -{ - Value v1, v2; - int r; - - PopValStack(v2); - if ( (r = FnPopValStack(&v1)) ) { - DestroyValue(v2); - return r; - } - - if (v1.type != v2.type || v1.type == STR_TYPE || v2.type == STR_TYPE) { - DestroyValue(v1); DestroyValue(v2); - return E_BAD_TYPE; - } - if (v1.v.val != 0) { - v1.v.val = v2.v.val; - } - PushValStack(v1); - return OK; -} - -/***************************************************************/ -/* */ -/* UnMinus */ -/* */ -/* Unary Minus */ -/* */ -/***************************************************************/ -static int UnMinus(void) -{ - Value *v = &ValStack[ValStackPtr-1]; - if (v->type != INT_TYPE) return E_BAD_TYPE; - if (v->v.val == INT_MIN) return E_2HIGH; - v->v.val = -v->v.val; - return OK; -} - -/***************************************************************/ -/* */ -/* LogNot */ -/* */ -/* Logical NOT */ -/* */ -/***************************************************************/ -static int LogNot(void) -{ - Value *v = &ValStack[ValStackPtr-1]; - if (v->type != INT_TYPE) return E_BAD_TYPE; - if (v->v.val) v->v.val = 0; else v->v.val = 1; - return OK; -} - -/***************************************************************/ -/* */ -/* FindOperator */ -/* */ -/* Find an operator. */ -/* */ -/***************************************************************/ -Operator *FindOperator(char const *name, Operator where[], int num) -{ - int top=num-1, bot=0; - int mid, r; - while (top >= bot) { - mid = (top + bot) / 2; - r = strcmp(name, where[mid].name); - if (!r) return &where[mid]; - else if (r > 0) bot = mid+1; - else top = mid-1; - } - return NULL; -} - -/* Compare two strings case-insensitively, where we KNOW - that the second string is definitely lower-case */ -static int strcmp_lcfirst(char const *s1, char const *s2) -{ - int r; - while (*s1 && *s2) { - r = tolower(*s1) - *s2; - if (r) return r; - s1++; - s2++; - } - return tolower(*s1) - *s2; -} - -/***************************************************************/ -/* */ -/* FindFunc */ -/* */ -/* Find a function. */ -/* */ -/***************************************************************/ -BuiltinFunc *FindFunc(char const *name, BuiltinFunc where[], int num) -{ - int top=num-1, bot=0; - int mid, r; - while (top >= bot) { - mid = (top + bot) / 2; - r = strcmp_lcfirst(name, where[mid].name); - if (!r) return &where[mid]; - else if (r > 0) bot = mid+1; - else top = mid-1; - } - return NULL; -} - -/***************************************************************/ -/* */ -/* PrintValue */ -/* */ -/* Print a value to stdout for debugging purposes. */ -/* */ -/***************************************************************/ -void PrintValue (Value *v, FILE *fp) -{ - int y, m, d; - unsigned char const *s; - - if (v->type == STR_TYPE) { - s = (unsigned char const *) v->v.str; - putc('"', fp); - for (y=0; ytype == INT_TYPE) fprintf(fp, "%d", v->v.val); - else if (v->type == TIME_TYPE) fprintf(fp, "%02d%c%02d", v->v.val / 60, - TimeSep, v->v.val % 60); - else if (v->type == DATE_TYPE) { - FromDSE(v->v.val, &y, &m, &d); - fprintf(fp, "%04d%c%02d%c%02d", y, DateSep, m+1, DateSep, d); - } - else if (v->type == DATETIME_TYPE) { - FromDSE(v->v.val / MINUTES_PER_DAY, &y, &m, &d); - 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 fprintf(fp, "ERR"); -} - -/***************************************************************/ -/* */ -/* CopyValue */ -/* */ -/* Copy a value. */ -/* */ -/***************************************************************/ -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; -} - -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; -} - -/***************************************************************/ -/* */ -/* ParseLiteralDate */ -/* */ -/* Parse a literal date or datetime. Return result in dse */ -/* and tim; update s. */ -/* */ -/***************************************************************/ -int ParseLiteralDate(char const **s, int *dse, int *tim) -{ - int y, m, d; - int r; - - y=0; m=0; d=0; - - *tim = NO_TIME; - if (!isdigit(**s)) return E_BAD_DATE; - while (isdigit(**s)) { - y *= 10; - y += *(*s)++ - '0'; - } - 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; -} - -/***************************************************************/ -/* */ -/* FnPopValStack */ -/* */ -/* Pop a value from the value stack - implemented as a */ -/* function for situations where we don't want an immediate */ -/* return upon failure. */ -/* */ -/***************************************************************/ -int FnPopValStack(Value *val) -{ - if (ValStackPtr <= 0) - return E_VA_STK_UNDER; - else { - *val = ValStack[--ValStackPtr]; - return OK; - } -} - -void DebugExitFunc(void) -{ - if (DebugFlag & DB_EXPR_STACKS) { - fprintf(stderr, "Operator stack high water: %d\n", OpStackHiWater); - fprintf(stderr, " Value stack high water: %d\n", ValStackHiWater); - } + fprintf(stderr, " Expression nodes allocated: %d\n", ExprNodesAllocated); + fprintf(stderr, "Expression nodes high-water: %d\n", ExprNodesHighWater); + fprintf(stderr, " Expression nodes leaked: %d\n", ExprNodesUsed); } diff --git a/src/expr.h b/src/expr.h index 4999511f..9623a084 100644 --- a/src/expr.h +++ b/src/expr.h @@ -1,15 +1,19 @@ /***************************************************************/ /* */ -/* EXPR.H */ +/* EXPR_NEW.H */ /* */ -/* Contains a few definitions used by expression evaluator. */ +/* Contains a few definitions used by expression pareser and */ +/* evaluator. */ /* */ /* This file is part of REMIND. */ -/* Copyright (C) 1992-2024 by Dianne Skoll */ -/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2022 by Dianne Skoll */ /* */ /***************************************************************/ +typedef struct udf_struct UserFunc; + +expr_node *parse_expression(char const **e, int *r, Var *locals); + /* Define the types of values */ #define ERR_TYPE 0 #define INT_TYPE 1 @@ -20,43 +24,10 @@ #define SPECIAL_TYPE 6 /* Only for system variables */ #define CONST_INT_TYPE 7 /* Only for system variables */ -/* Define stuff for parsing expressions */ #define BEG_OF_EXPR '[' #define END_OF_EXPR ']' #define COMMA ',' -#define UN_OP 0 /* Unary operator */ -#define BIN_OP 1 /* Binary Operator */ -#define FUNC 2 /* Function */ - -/* Make the pushing and popping of values and operators in-line code - for speed. BEWARE: These macros invoke return if an error happens ! */ - -#define PushOpStack(op) \ - do { if (OpStackPtr >= OP_STACK_SIZE) return E_OP_STK_OVER; \ - else { OpStack[OpStackPtr++] = (op); if (OpStackPtr > OpStackHiWater) OpStackHiWater = OpStackPtr; } } while(0) - -#define PopOpStack(op) \ -if (OpStackPtr <= 0) \ -return E_OP_STK_UNDER; \ -else \ -(op) = OpStack[--OpStackPtr] - -#define PushValStack(val) \ -do { if (ValStackPtr >= VAL_STACK_SIZE) { \ - DestroyValue(val); \ - return E_VA_STK_OVER; \ -} else { \ - ValStack[ValStackPtr++] = (val); \ - if (ValStackPtr > ValStackHiWater) ValStackHiWater = ValStackPtr; \ -} } while (0); - -#define PopValStack(val) \ -if (ValStackPtr <= 0) \ -return E_VA_STK_UNDER; \ -else \ -(val) = ValStack[--ValStackPtr] - /* These functions are in utils.c and are used to detect overflow in various arithmetic operators. They have to be in separate functions with extern linkage to defeat compiler optimizations @@ -64,4 +35,3 @@ else \ extern int _private_mul_overflow(int a, int b); extern int _private_add_overflow(int a, int b); extern int _private_sub_overflow(int a, int b); - diff --git a/src/funcs.c b/src/funcs.c index 45001f7d..92005e17 100644 --- a/src/funcs.c +++ b/src/funcs.c @@ -60,6 +60,8 @@ #define Nargs (info->nargs) #define RetVal (info->retval) +#define DBG(x) do { if (DebugFlag & DB_PRTEXPR) { x; } } while(0) + static int solstice_equinox_for_year(int y, int which); @@ -74,7 +76,7 @@ static int FArgs (func_info *); static int FAsc (func_info *); static int FBaseyr (func_info *); static int FChar (func_info *); -static int FChoose (func_info *); +static int FChoose (expr_node *, Value *, Value *, int *); static int FCoerce (func_info *); static int FColumns (func_info *); static int FCurrent (func_info *); @@ -101,7 +103,7 @@ static int FHebyear (func_info *); static int FHour (func_info *); static int FHtmlEscape (func_info *); static int FHtmlStriptags (func_info *); -static int FIif (func_info *); +static int FIif (expr_node *, Value *, Value *, int *); static int FIndex (func_info *); static int FIsAny (func_info *); static int FIsdst (func_info *); @@ -180,14 +182,9 @@ static int FWkday (func_info *); static int FWkdaynum (func_info *); static int FYear (func_info *); -static int CleanUpAfterFunc (func_info *); -static int CheckArgs (BuiltinFunc *f, int nargs); static int SunStuff (int rise, double cosz, int dse); static int tz_set_tz (char const *tz); -/* "Overload" the struct Operator definition */ -#define NO_MAX 127 - /* Caches for extracting months, days, years from dates - may improve performance slightly. */ static int CacheDse = -1; @@ -196,14 +193,9 @@ static int CacheYear, CacheMon, CacheDay; static int CacheHebDse = -1; static int CacheHebYear, CacheHebMon, CacheHebDay; -/* We need access to the value stack */ -extern Value ValStack[]; -extern int ValStackPtr; -extern int ValStackHiWater; - /* Macro for accessing arguments from the value stack - args are numbered from 0 to (Nargs - 1) */ -#define ARG(x) (ValStack[ValStackPtr - Nargs + (x)]) +#define ARG(x) (info->args[x]) #define ARGV(x) ARG(x).v.val #define ARGSTR(x) ARG(x).v.str @@ -231,215 +223,127 @@ extern int ValStackHiWater; /* The array holding the built-in functions. */ BuiltinFunc Func[] = { -/* Name minargs maxargs is_constant func */ +/* Name minargs maxargs is_constant func newfunc*/ - { "abs", 1, 1, 1, FAbs }, - { "access", 2, 2, 0, FAccess }, - { "adawn", 0, 1, 0, FADawn}, - { "adusk", 0, 1, 0, FADusk}, - { "ampm", 1, 3, 1, FAmpm }, - { "ansicolor", 1, 5, 1, FAnsicolor }, - { "args", 1, 1, 0, FArgs }, - { "asc", 1, 1, 1, FAsc }, - { "baseyr", 0, 0, 1, FBaseyr }, - { "char", 1, NO_MAX, 1, FChar }, - { "choose", 2, NO_MAX, 1, FChoose }, - { "coerce", 2, 2, 1, FCoerce }, - { "columns", 0, 1, 0, FColumns }, - { "current", 0, 0, 0, FCurrent }, - { "date", 3, 3, 1, FDate }, - { "datepart", 1, 1, 1, FDatepart }, - { "datetime", 2, 5, 1, FDateTime }, - { "dawn", 0, 1, 0, FDawn}, - { "day", 1, 1, 1, FDay }, - { "daysinmon", 2, 2, 1, FDaysinmon }, - { "defined", 1, 1, 0, FDefined }, - { "dosubst", 1, 3, 0, FDosubst }, - { "dusk", 0, 1, 0, FDusk }, - { "easterdate", 0, 1, 0, FEasterdate }, - { "evaltrig", 1, 2, 0, FEvalTrig }, - { "filedate", 1, 1, 0, FFiledate }, - { "filedatetime", 1, 1, 0, FFiledatetime }, - { "filedir", 0, 0, 0, FFiledir }, - { "filename", 0, 0, 0, FFilename }, - { "getenv", 1, 1, 0, FGetenv }, - { "hebdate", 2, 5, 0, FHebdate }, - { "hebday", 1, 1, 0, FHebday }, - { "hebmon", 1, 1, 0, FHebmon }, - { "hebyear", 1, 1, 0, FHebyear }, - { "hour", 1, 1, 1, FHour }, - { "htmlescape", 1, 1, 1, FHtmlEscape }, - { "htmlstriptags",1, 1, 1, FHtmlStriptags }, - { "iif", 1, NO_MAX, 1, FIif }, - { "index", 2, 3, 1, FIndex }, - { "isany", 1, NO_MAX, 1, FIsAny }, - { "isdst", 0, 2, 0, FIsdst }, - { "isleap", 1, 1, 1, FIsleap }, - { "isomitted", 1, 1, 0, FIsomitted }, - { "language", 0, 0, 1, FLanguage }, - { "localtoutc", 1, 1, 1, FLocalToUTC }, - { "lower", 1, 1, 1, FLower }, - { "max", 1, NO_MAX, 1, FMax }, - { "min", 1, NO_MAX, 1, FMin }, - { "minsfromutc", 0, 2, 0, FMinsfromutc }, - { "minute", 1, 1, 1, FMinute }, - { "mon", 1, 1, 1, FMon }, - { "monnum", 1, 1, 1, FMonnum }, - { "moondate", 1, 3, 0, FMoondate }, - { "moondatetime", 1, 3, 0, FMoondatetime }, - { "moonphase", 0, 2, 0, FMoonphase }, - { "moontime", 1, 3, 0, FMoontime }, - { "multitrig", 1, NO_MAX, 0, FMultiTrig }, - { "ndawn", 0, 1, 0, FNDawn}, - { "ndusk", 0, 1, 0, FNDusk}, - { "nonomitted", 2, NO_MAX, 0, FNonomitted }, - { "now", 0, 0, 0, FNow }, - { "ord", 1, 1, 1, FOrd }, - { "orthodoxeaster",0, 1, 0, FOrthodoxeaster }, - { "ostype", 0, 0, 1, FOstype }, - { "pad", 3, 4, 1, FPad }, - { "plural", 1, 3, 1, FPlural }, - { "psmoon", 1, 4, 1, FPsmoon}, - { "psshade", 1, 3, 1, FPsshade}, - { "realcurrent", 0, 0, 0, FRealCurrent}, - { "realnow", 0, 0, 0, FRealnow}, - { "realtoday", 0, 0, 0, FRealtoday }, - { "rows", 0, 0, 0, FRows }, - { "sgn", 1, 1, 1, FSgn }, - { "shell", 1, 2, 0, FShell }, - { "shellescape", 1, 1, 1, FShellescape }, - { "slide", 2, NO_MAX, 0, FSlide }, - { "soleq", 1, 2, 0, FSoleq }, - { "stdout", 0, 0, 1, FStdout }, - { "strlen", 1, 1, 1, FStrlen }, - { "substr", 2, 3, 1, FSubstr }, - { "sunrise", 0, 1, 0, FSunrise}, - { "sunset", 0, 1, 0, FSunset }, - { "time", 2, 2, 1, FTime }, - { "timepart", 1, 1, 1, FTimepart }, - { "timezone", 0, 1, 1, FTimezone }, - { "today", 0, 0, 0, FToday }, - { "trig", 0, NO_MAX, 0, FTrig }, - { "trigback", 0, 0, 0, FTrigback }, - { "trigdate", 0, 0, 0, FTrigdate }, - { "trigdatetime", 0, 0, 0, FTrigdatetime }, - { "trigdelta", 0, 0, 0, FTrigdelta }, - { "trigduration", 0, 0, 0, FTrigduration }, - { "trigeventduration", 0, 0, 0, FTrigeventduration }, - { "trigeventstart", 0, 0, 0, FTrigeventstart }, - { "trigfrom", 0, 0, 0, FTrigfrom }, - { "trigger", 1, 3, 0, FTrigger }, - { "trigpriority", 0, 0, 0, FTrigpriority }, - { "trigrep", 0, 0, 0, FTrigrep }, - { "trigscanfrom", 0, 0, 0, FTrigscanfrom }, - { "trigtags", 0, 0, 0, FTrigtags }, - { "trigtime", 0, 0, 0, FTrigtime }, - { "trigtimedelta",0, 0, 0, FTrigtimedelta }, - { "trigtimerep", 0, 0, 0, FTrigtimerep }, - { "triguntil", 0, 0, 0, FTriguntil }, - { "trigvalid", 0, 0, 0, FTrigvalid }, - { "typeof", 1, 1, 1, FTypeof }, - { "tzconvert", 2, 3, 0, FTzconvert }, - { "upper", 1, 1, 1, FUpper }, - { "utctolocal", 1, 1, 1, FUTCToLocal }, - { "value", 1, 2, 0, FValue }, - { "version", 0, 0, 1, FVersion }, - { "weekno", 0, 3, 1, FWeekno }, - { "wkday", 1, 1, 1, FWkday }, - { "wkdaynum", 1, 1, 1, FWkdaynum }, - { "year", 1, 1, 1, FYear } + { "abs", 1, 1, 1, FAbs, NULL }, + { "access", 2, 2, 0, FAccess, NULL }, + { "adawn", 0, 1, 0, FADawn, NULL}, + { "adusk", 0, 1, 0, FADusk, NULL}, + { "ampm", 1, 3, 1, FAmpm, NULL }, + { "ansicolor", 1, 5, 1, FAnsicolor, NULL }, + { "args", 1, 1, 0, FArgs, NULL }, + { "asc", 1, 1, 1, FAsc, NULL }, + { "baseyr", 0, 0, 1, FBaseyr, NULL }, + { "char", 1, NO_MAX, 1, FChar, NULL }, + { "choose", 2, NO_MAX, 1, NULL, FChoose }, /*NEW-STYLE*/ + { "coerce", 2, 2, 1, FCoerce, NULL }, + { "columns", 0, 1, 0, FColumns, NULL }, + { "current", 0, 0, 0, FCurrent, NULL }, + { "date", 3, 3, 1, FDate, NULL }, + { "datepart", 1, 1, 1, FDatepart, NULL }, + { "datetime", 2, 5, 1, FDateTime, NULL }, + { "dawn", 0, 1, 0, FDawn, NULL }, + { "day", 1, 1, 1, FDay, NULL }, + { "daysinmon", 2, 2, 1, FDaysinmon, NULL }, + { "defined", 1, 1, 0, FDefined, NULL }, + { "dosubst", 1, 3, 0, FDosubst, NULL }, + { "dusk", 0, 1, 0, FDusk, NULL }, + { "easterdate", 0, 1, 0, FEasterdate, NULL }, + { "evaltrig", 1, 2, 0, FEvalTrig, NULL }, + { "filedate", 1, 1, 0, FFiledate, NULL }, + { "filedatetime", 1, 1, 0, FFiledatetime, NULL }, + { "filedir", 0, 0, 0, FFiledir, NULL }, + { "filename", 0, 0, 0, FFilename, NULL }, + { "getenv", 1, 1, 0, FGetenv, NULL }, + { "hebdate", 2, 5, 0, FHebdate, NULL }, + { "hebday", 1, 1, 0, FHebday, NULL }, + { "hebmon", 1, 1, 0, FHebmon, NULL }, + { "hebyear", 1, 1, 0, FHebyear, NULL }, + { "hour", 1, 1, 1, FHour, NULL }, + { "htmlescape", 1, 1, 1, FHtmlEscape, NULL }, + { "htmlstriptags",1, 1, 1, FHtmlStriptags, NULL }, + { "iif", 1, NO_MAX, 1, NULL, FIif }, /*NEW-STYLE*/ + { "index", 2, 3, 1, FIndex, NULL }, + { "isany", 1, NO_MAX, 1, FIsAny, NULL }, + { "isdst", 0, 2, 0, FIsdst, NULL }, + { "isleap", 1, 1, 1, FIsleap, NULL }, + { "isomitted", 1, 1, 0, FIsomitted, NULL }, + { "language", 0, 0, 1, FLanguage, NULL }, + { "localtoutc", 1, 1, 1, FLocalToUTC, NULL }, + { "lower", 1, 1, 1, FLower, NULL }, + { "max", 1, NO_MAX, 1, FMax, NULL }, + { "min", 1, NO_MAX, 1, FMin, NULL }, + { "minsfromutc", 0, 2, 0, FMinsfromutc, NULL }, + { "minute", 1, 1, 1, FMinute, NULL }, + { "mon", 1, 1, 1, FMon, NULL }, + { "monnum", 1, 1, 1, FMonnum, NULL }, + { "moondate", 1, 3, 0, FMoondate, NULL }, + { "moondatetime", 1, 3, 0, FMoondatetime, NULL }, + { "moonphase", 0, 2, 0, FMoonphase, NULL }, + { "moontime", 1, 3, 0, FMoontime, NULL }, + { "multitrig", 1, NO_MAX, 0, FMultiTrig, NULL }, + { "ndawn", 0, 1, 0, FNDawn, NULL }, + { "ndusk", 0, 1, 0, FNDusk, NULL }, + { "nonomitted", 2, NO_MAX, 0, FNonomitted, NULL }, + { "now", 0, 0, 0, FNow, NULL }, + { "ord", 1, 1, 1, FOrd, NULL }, + { "orthodoxeaster",0, 1, 0, FOrthodoxeaster, NULL }, + { "ostype", 0, 0, 1, FOstype, NULL }, + { "pad", 3, 4, 1, FPad, NULL }, + { "plural", 1, 3, 1, FPlural, NULL }, + { "psmoon", 1, 4, 1, FPsmoon, NULL }, + { "psshade", 1, 3, 1, FPsshade, NULL }, + { "realcurrent", 0, 0, 0, FRealCurrent, NULL }, + { "realnow", 0, 0, 0, FRealnow, NULL }, + { "realtoday", 0, 0, 0, FRealtoday, NULL }, + { "rows", 0, 0, 0, FRows, NULL }, + { "sgn", 1, 1, 1, FSgn, NULL }, + { "shell", 1, 2, 0, FShell, NULL }, + { "shellescape", 1, 1, 1, FShellescape, NULL }, + { "slide", 2, NO_MAX, 0, FSlide, NULL }, + { "soleq", 1, 2, 0, FSoleq, NULL }, + { "stdout", 0, 0, 1, FStdout, NULL }, + { "strlen", 1, 1, 1, FStrlen, NULL }, + { "substr", 2, 3, 1, FSubstr, NULL }, + { "sunrise", 0, 1, 0, FSunrise, NULL }, + { "sunset", 0, 1, 0, FSunset, NULL }, + { "time", 2, 2, 1, FTime, NULL }, + { "timepart", 1, 1, 1, FTimepart, NULL }, + { "timezone", 0, 1, 1, FTimezone, NULL }, + { "today", 0, 0, 0, FToday, NULL }, + { "trig", 0, NO_MAX, 0, FTrig, NULL }, + { "trigback", 0, 0, 0, FTrigback, NULL }, + { "trigdate", 0, 0, 0, FTrigdate, NULL }, + { "trigdatetime", 0, 0, 0, FTrigdatetime, NULL }, + { "trigdelta", 0, 0, 0, FTrigdelta, NULL }, + { "trigduration", 0, 0, 0, FTrigduration, NULL }, + { "trigeventduration", 0, 0, 0, FTrigeventduration, NULL }, + { "trigeventstart", 0, 0, 0, FTrigeventstart, NULL }, + { "trigfrom", 0, 0, 0, FTrigfrom, NULL }, + { "trigger", 1, 3, 0, FTrigger, NULL }, + { "trigpriority", 0, 0, 0, FTrigpriority, NULL }, + { "trigrep", 0, 0, 0, FTrigrep, NULL }, + { "trigscanfrom", 0, 0, 0, FTrigscanfrom, NULL }, + { "trigtags", 0, 0, 0, FTrigtags, NULL }, + { "trigtime", 0, 0, 0, FTrigtime, NULL }, + { "trigtimedelta",0, 0, 0, FTrigtimedelta, NULL }, + { "trigtimerep", 0, 0, 0, FTrigtimerep, NULL }, + { "triguntil", 0, 0, 0, FTriguntil, NULL }, + { "trigvalid", 0, 0, 0, FTrigvalid, NULL }, + { "typeof", 1, 1, 1, FTypeof, NULL }, + { "tzconvert", 2, 3, 0, FTzconvert, NULL }, + { "upper", 1, 1, 1, FUpper, NULL }, + { "utctolocal", 1, 1, 1, FUTCToLocal, NULL }, + { "value", 1, 2, 0, FValue, NULL }, + { "version", 0, 0, 1, FVersion, NULL }, + { "weekno", 0, 3, 1, FWeekno, NULL }, + { "wkday", 1, 1, 1, FWkday, NULL }, + { "wkdaynum", 1, 1, 1, FWkdaynum, NULL }, + { "year", 1, 1, 1, FYear, NULL } }; /* Need a variable here - Func[] array not really visible to outside. */ -int NumFuncs = sizeof(Func) / sizeof(Operator) ; - -/***************************************************************/ -/* */ -/* CallFunc */ -/* */ -/* Call a function given a pointer to it, and the number */ -/* of arguments supplied. */ -/* */ -/***************************************************************/ -int CallFunc(BuiltinFunc *f, int nargs) -{ - register int r = CheckArgs(f, nargs); - int i; - - func_info info_obj; - func_info *info = &info_obj; - - Nargs = nargs; - RetVal.type = ERR_TYPE; - - if (DebugFlag & DB_PRTEXPR) { - fprintf(ErrFp, "%s(", f->name); - for (i=0; i "); - if (r) { - fprintf(ErrFp, "%s\n", ErrMsg[r]); - return r; - } - } - if (r) { - Eprint("%s(): %s", f->name, ErrMsg[r]); - return r; - } - - r = (*(f->func))(info); - if (r) { - DestroyValue(RetVal); - if (DebugFlag & DB_PRTEXPR) - fprintf(ErrFp, "%s\n", ErrMsg[r]); - else - Eprint("%s(): %s", f->name, ErrMsg[r]); - return r; - } - if (DebugFlag & DB_PRTEXPR) { - PrintValue(&RetVal, ErrFp); - fprintf(ErrFp, "\n"); - } - r = CleanUpAfterFunc(info); - return r; -} - -/***************************************************************/ -/* */ -/* CheckArgs */ -/* */ -/* Check that the right number of args have been supplied */ -/* for a function. */ -/* */ -/***************************************************************/ -static int CheckArgs(BuiltinFunc *f, int nargs) -{ - if (nargs < f->minargs) return E_2FEW_ARGS; - if (nargs > f->maxargs && f->maxargs != NO_MAX) return E_2MANY_ARGS; - return OK; -} -/***************************************************************/ -/* */ -/* CleanUpAfterFunc */ -/* */ -/* Clean up the stack after a function call - remove */ -/* args and push the new value. */ -/* */ -/***************************************************************/ -static int CleanUpAfterFunc(func_info *info) -{ - Value v; - int i; - - for (i=0; inum_kids; + Value(v); + DBG(DBufInit(&DebugBuf)); + PUT("choose("); - ASSERT_TYPE(0, INT_TYPE); - v = ARGV(0); - if (v < 1) v = 1; - if (v > Nargs-1) v = Nargs-1; - DCOPYVAL(RetVal, ARG(v)); + cur = node->child; + r = evaluate_expr_node(cur, locals, &v, nonconst); + if (r != OK) { + DBG(DBufFree(&DebugBuf)); + return r; + } + DBG(PUT(PrintValue(&v, NULL))); + if (v.type != INT_TYPE) { + if (DebugFlag & DB_PRTEXPR) { + cur = cur->sibling; + while(cur) { + PUT(", ?"); + cur = cur->sibling; + } + PUT(") => "); + PUT(ErrMsg[E_BAD_TYPE]); + OUT(); + } + return E_BAD_TYPE; + } + n = v.v.val; + if (n < 1) n = 1; + if (n > nargs-1) n = nargs-1; + + while(n--) { + cur = cur->sibling; + DBG(if (n) { PUT(", ?"); }); + if (!cur) return E_SWERR; /* Should not happen! */ + } + r = evaluate_expr_node(cur, locals, ans, nonconst); + if (r != OK) { + DBG(DBufFree(&DebugBuf)); + return r; + } + if (DebugFlag & DB_PRTEXPR) { + PUT(", "); + PUT(PrintValue(ans, NULL)); + cur = cur->sibling; + while(cur) { + PUT(", ?"); + cur = cur->sibling; + } + PUT(") => "); + PUT(PrintValue(ans, NULL)); + OUT(); + } return OK; } @@ -1481,7 +1436,7 @@ static int FValue(func_info *info) ASSERT_TYPE(0, STR_TYPE); switch(Nargs) { case 1: - return GetVarValue(ARGSTR(0), &RetVal, NULL, NULL); + return GetVarValue(ARGSTR(0), &RetVal); case 2: v = FindVar(ARGSTR(0), 0); @@ -1953,33 +1908,101 @@ static int FIndex(func_info *info) /* */ /* FIif */ /* */ -/* The IIF function. */ +/* The IIF function. Uses new-style evaluation */ /* */ /***************************************************************/ -static int FIif(func_info *info) +static int FIif(expr_node *node, Value *locals, Value *ans, int *nonconst) { int istrue; - int arg; + int r; + int done; + Value v; + expr_node *cur; + DynamicBuffer DebugBuf; - if (!(Nargs % 2)) return E_IIF_ODD; + DBG(DBufInit(&DebugBuf)); + DBG(PUT("iif(")); + cur = node->child; - for (arg=0; argnum_kids % 2)) { + if (DebugFlag & DB_PRTEXPR) { + r = 0; + while(cur) { + if (r) PUT(", "); + r=1; + PUT("?"); + cur = cur->sibling; + } + PUT(") => "); + PUT(ErrMsg[E_IIF_ODD]); + OUT(); + } + return E_IIF_ODD; } - DCOPYVAL(RetVal, ARG(Nargs-1)); - return OK; + + done = 0; + while(cur->sibling) { + r = evaluate_expr_node(cur, locals, &v, nonconst); + if (r != OK) { + DBG(DBufFree(&DebugBuf)); + return r; + } + if (DebugFlag & DB_PRTEXPR) { + if (done) PUT(", "); + done = 1; + PUT(PrintValue(&v, NULL)); + } + if (v.type != STR_TYPE && v.type != INT_TYPE) { + if (DebugFlag & DB_PRTEXPR) { + cur = cur->sibling; + while(cur) { + PUT(", ?"); + cur = cur->sibling; + } + PUT(") => "); + PUT(ErrMsg[E_BAD_TYPE]); + OUT(); + } + return E_BAD_TYPE; + } + + if (v.type == INT_TYPE) { + istrue = v.v.val; + } else { + istrue = *(v.v.str); + } + if (istrue) { + r = evaluate_expr_node(cur->sibling, locals, ans, nonconst); + if (r == OK && (DebugFlag & DB_PRTEXPR)) { + PUT(", "); + PUT(PrintValue(ans, NULL)); + cur = cur->sibling->sibling; + while(cur) { + PUT(", ?"); + cur = cur->sibling; + } + PUT(") => "); + PUT(PrintValue(ans, NULL)); + OUT(); + } + DBG(DBufFree(&DebugBuf)); + return r; + } + DBG(PUT(", ?")); + cur = cur->sibling->sibling; + } + + /* Return the last arg */ + r = evaluate_expr_node(cur, locals, ans, nonconst); + if (DebugFlag & DB_PRTEXPR) { + if (done) PUT(", "); + PUT(PrintValue(ans, NULL)); + PUT(") => "); + PUT(PrintValue(ans, NULL)); + OUT(); + } + return r; } /***************************************************************/ @@ -2373,14 +2396,18 @@ static int FEasterdate(func_info *info) { int y, m, d; int g, c, x, z, e, n; + int base; if (Nargs == 0) { + base = DSEToday; FromDSE(DSEToday, &y, &m, &d); } else { if (ARG(0).type == INT_TYPE) { + base = -1; y = ARGV(0); if (y < BASE) return E_2LOW; else if (y > BASE+YR_RANGE) return E_2HIGH; } else if (HASDATE(ARG(0))) { + base = DATEPART(ARG(0)); FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */ } else return E_BAD_TYPE; } @@ -2406,7 +2433,7 @@ static int FEasterdate(func_info *info) RetVal.type = DATE_TYPE; RETVAL = DSE(y, m, d); - y++; } while (HASDATE(ARG(0)) && RETVAL < DATEPART(ARG(0))); + y++; } while (base > -1 && RETVAL < base); return OK; } @@ -2422,7 +2449,9 @@ static int FOrthodoxeaster(func_info *info) { int y, m, d; int a, b, c, dd, e, f, dse; + int base = -1; if (Nargs == 0) { + base = DSEToday; FromDSE(DSEToday, &y, &m, &d); } else { if (ARG(0).type == INT_TYPE) { @@ -2430,6 +2459,7 @@ static int FOrthodoxeaster(func_info *info) if (y < BASE) return E_2LOW; else if (y > BASE+YR_RANGE) return E_2HIGH; } else if (HASDATE(ARG(0))) { + base = DATEPART(ARG(0)); FromDSE(DATEPART(ARG(0)), &y, &m, &d); /* We just want the year */ } else return E_BAD_TYPE; } @@ -2449,7 +2479,7 @@ static int FOrthodoxeaster(func_info *info) RetVal.type = DATE_TYPE; RETVAL = dse; y++; - } while (HASDATE(ARG(0)) && RETVAL < DATEPART(ARG(0))); + } while (base > -1 && RETVAL < base); return OK; } @@ -3896,3 +3926,38 @@ FSoleq(func_info *info) RETVAL = ret; return OK; } + +/* Compare two strings case-insensitively, where we KNOW + that the second string is definitely lower-case */ +static int strcmp_lcfirst(char const *s1, char const *s2) +{ + int r; + while (*s1 && *s2) { + r = tolower(*s1) - *s2; + if (r) return r; + s1++; + s2++; + } + return tolower(*s1) - *s2; +} + +/***************************************************************/ +/* */ +/* FindBuiltinFunc */ +/* */ +/* Find a built-in function. */ +/* */ +/***************************************************************/ +BuiltinFunc *FindBuiltinFunc(char const *name) +{ + int top=NumFuncs-1, bot=0; + int mid, r; + while (top >= bot) { + mid = (top + bot) / 2; + r = strcmp_lcfirst(name, Func[mid].name); + if (!r) return &Func[mid]; + else if (r > 0) bot = mid+1; + else top = mid-1; + } + return NULL; +} diff --git a/src/globals.h b/src/globals.h index 54a472b7..b270eb8d 100644 --- a/src/globals.h +++ b/src/globals.h @@ -159,6 +159,9 @@ EXTERN DynamicBuffer Banner; EXTERN DynamicBuffer LineBuffer; EXTERN DynamicBuffer ExprBuf; +/* User-func recursion level */ +EXTERN INIT( unsigned int FuncRecursionLevel, 0); + extern int NumFullOmits, NumPartialOmits; /* List of months */ diff --git a/src/init.c b/src/init.c index db336946..b54a949f 100644 --- a/src/init.c +++ b/src/init.c @@ -605,7 +605,7 @@ void InitRemind(int argc, char const *argv[]) case 'D': while (*arg) { switch(*arg++) { - case 's': case 'S': DebugFlag |= DB_EXPR_STACKS; break; + case 's': case 'S': DebugFlag |= DB_PARSE_EXPR; break; case 'e': case 'E': DebugFlag |= DB_ECHO_LINE; break; case 'x': case 'X': DebugFlag |= DB_PRTEXPR; break; case 't': case 'T': DebugFlag |= DB_PRTTRIG; break; diff --git a/src/langs/finnish.h b/src/langs/finnish.h index 7d88f2dd..0751e5f8 100644 --- a/src/langs/finnish.h +++ b/src/langs/finnish.h @@ -246,7 +246,9 @@ EXTERN char *ErrMsg[] = "String too long", "Time specified twice", "Cannot specify DURATION without specifying AT", - "Odotettu viikonpäivän nimi" + "Odotettu viikonpäivän nimi", + "Päällekkäinen argumentin nimi" + }; #endif /* MK_GLOBALS */ diff --git a/src/langs/french.h b/src/langs/french.h index 86bcb256..aac1f0e0 100644 --- a/src/langs/french.h +++ b/src/langs/french.h @@ -221,6 +221,7 @@ EXTERN char *ErrMsg[] = "Time specified twice", "Cannot specify DURATION without specifying AT", "Nom du jour de la semaine attendu", + "Nom de l'argument en double", }; #endif /* MK_GLOBALS */ diff --git a/src/langs/polish.h b/src/langs/polish.h index c2c6f4ff..9e135d5e 100644 --- a/src/langs/polish.h +++ b/src/langs/polish.h @@ -236,7 +236,8 @@ EXTERN char *ErrMsg[] = "String too long", "Time specified twice", "Cannot specify DURATION without specifying AT", - "Oczekiwana nazwa dnia tygodnia" + "Oczekiwana nazwa dnia tygodnia", + "Zduplikowana nazwa argumentu", }; #endif /* MK_GLOBALS */ diff --git a/src/langs/portbr.h b/src/langs/portbr.h index d8ff1a54..95591376 100644 --- a/src/langs/portbr.h +++ b/src/langs/portbr.h @@ -246,6 +246,7 @@ EXTERN char *ErrMsg[] = "Time specified twice", "Cannot specify DURATION without specifying AT", "Esperando nome do dia da semana", + "Nome de argumento duplicado" }; #endif /* MK_GLOBALS */ diff --git a/src/main.c b/src/main.c index ac5765f0..40ebaa6e 100644 --- a/src/main.c +++ b/src/main.c @@ -55,6 +55,15 @@ static void DoReminders(void); /* Macro for simplifying common block so as not to litter code */ #define OUTPUT(c) do { if (output) { DBufPutc(output, c); } else { putchar(c); } } while(0) +void +exitfunc(void) +{ + if (DebugFlag & DB_PARSE_EXPR) { + UnsetAllUserFuncs(); + print_expr_nodes_stats(); + } +} + /***************************************************************/ /***************************************************************/ /** **/ @@ -81,7 +90,7 @@ int main(int argc, char *argv[]) DBufInit(&(LastTrigger.tags)); ClearLastTriggers(); - atexit(DebugExitFunc); + atexit(exitfunc); if (DoCalendar || (DoSimpleCalendar && (!NextMode || PsCal))) { ProduceCalendar(); @@ -289,6 +298,7 @@ static void DoReminders(void) case T_RemType: if (tok.val == RUN_TYPE) { r=DoRun(&p); } else { + DestroyParser(&p); CreateParser(CurLine, &p); r=DoRem(&p); purge_handled = 1; @@ -297,10 +307,8 @@ static void DoReminders(void) /* If we don't recognize the command, do a REM by default */ - /* Note: Since the parser hasn't been used yet, we don't */ - /* need to destroy it here. */ - default: CreateParser(CurLine, &p); purge_handled = 1; r=DoRem(&p); break; + default: DestroyParser(&p); CreateParser(CurLine, &p); purge_handled = 1; r=DoRem(&p); break; } if (r && (!Hush || r != E_RUN_DISABLED)) { @@ -582,6 +590,53 @@ int ParseIdentifier(ParsePtr p, DynamicBuffer *dbuf) } } +/***************************************************************/ +/* */ +/* ParseExpr */ +/* */ +/* We are expecting an expression here. Parse it and return */ +/* the value node tree. */ +/* */ +/***************************************************************/ +expr_node * ParseExpr(ParsePtr p, int *r) +{ + + int bracketed = 0; + expr_node *node; + + if (p->isnested) { + *r = E_PARSE_ERR; /* Can't nest expressions */ + return NULL; + } + if (!p->pos) { + *r = E_PARSE_ERR; /* Missing expression */ + return NULL; + } + + while (isempty(*p->pos)) (p->pos)++; + if (!*(p->pos)) { + *r = E_EOLN; + return NULL; + } + if (*p->pos == BEG_OF_EXPR) { + (p->pos)++; + bracketed = 1; + } + node = parse_expression(&(p->pos), r, NULL); + if (*r) { + return free_expr_tree(node); + } + + if (bracketed) { + if (*p->pos != END_OF_EXPR) { + *r = E_MISS_END; + return free_expr_tree(node); + } + (p->pos)++; + } + return node; +} + /***************************************************************/ /* */ /* EvaluateExpr */ @@ -593,21 +648,22 @@ int ParseIdentifier(ParsePtr p, DynamicBuffer *dbuf) int EvaluateExpr(ParsePtr p, Value *v) { - int bracketed = 0; int r; + int nonconst = 0; + expr_node *node = ParseExpr(p, &r); - if (p->isnested) return E_PARSE_ERR; /* Can't nest expressions */ - if (!p->pos) return E_PARSE_ERR; /* Missing expression */ - while (isempty(*p->pos)) (p->pos)++; - if (*p->pos == BEG_OF_EXPR) { - (p->pos)++; - bracketed = 1; + if (r != OK) { + return r; } - r = EvalExpr(&(p->pos), v, p); + if (!node) { + return E_SWERR; + } + + r = evaluate_expr_node(node, NULL, v, &nonconst); + free_expr_tree(node); if (r) return r; - if (bracketed) { - if (*p->pos != END_OF_EXPR) return E_MISS_END; - (p->pos)++; + if (nonconst) { + p->nonconst_expr = 1; } return OK; } @@ -651,27 +707,34 @@ void Eprint(char const *fmt, ...) FreshLine = 0; if (strcmp(FileName, "-")) { (void) fprintf(ErrFp, "%s(%d): ", FileName, LineNo); + va_start(argptr, fmt); + (void) vfprintf(ErrFp, fmt, argptr); + (void) fputc('\n', ErrFp); + va_end(argptr); if (print_callstack(ErrFp)) { - (void) fprintf(ErrFp, ": "); + (void) fprintf(ErrFp, "\n"); } } else { (void) fprintf(ErrFp, "-stdin-(%d): ", LineNo); + va_start(argptr, fmt); + (void) vfprintf(ErrFp, fmt, argptr); + (void) fputc('\n', ErrFp); + va_end(argptr); if (print_callstack(ErrFp)) { - (void) fprintf(ErrFp, ": "); + (void) fprintf(ErrFp, "\n"); } } if (DebugFlag & DB_PRTLINE) OutputLine(ErrFp); } else if (FileName) { fprintf(ErrFp, " "); + va_start(argptr, fmt); + (void) vfprintf(ErrFp, fmt, argptr); + (void) fputc('\n', ErrFp); + va_end(argptr); if (print_callstack(ErrFp)) { - (void) fprintf(ErrFp, ": "); + (void) fprintf(ErrFp, "\n"); } } - - va_start(argptr, fmt); - (void) vfprintf(ErrFp, fmt, argptr); - (void) fputc('\n', ErrFp); - va_end(argptr); return; } @@ -1025,8 +1088,8 @@ int DoDebug(ParsePtr p) case 's': case 'S': - if (val) DebugFlag |= DB_EXPR_STACKS; - else DebugFlag &= ~DB_EXPR_STACKS; + if (val) DebugFlag |= DB_PARSE_EXPR; + else DebugFlag &= ~DB_PARSE_EXPR; break; case 'x': diff --git a/src/protos.h b/src/protos.h index efa34310..e57e5a3a 100644 --- a/src/protos.h +++ b/src/protos.h @@ -43,6 +43,7 @@ int CallUserFunc (char const *name, int nargs, ParsePtr p); int DoFset (ParsePtr p); int DoFunset (ParsePtr p); +void UnsetAllUserFuncs(void); void ProduceCalendar (void); char const *SimpleTime (int tim); char const *CalendarTime (int tim, int duration); @@ -56,9 +57,12 @@ int DoSubst (ParsePtr p, DynamicBuffer *dbuf, Trigger *t, TimeTrig *tt, int dse, int DoSubstFromString (char const *source, DynamicBuffer *dbuf, int dse, int tim); int ParseLiteralDate (char const **s, int *dse, int *tim); int ParseLiteralTime (char const **s, int *tim); +int evaluate_expr_node(expr_node *node, Value *locals, Value *ans, int *nonconst); +void print_expr_tree(expr_node *node, FILE *fp); +expr_node *free_expr_tree(expr_node *node); int EvalExpr (char const **e, Value *v, ParsePtr p); int DoCoerce (char type, Value *v); -void PrintValue (Value *v, FILE *fp); +char const *PrintValue (Value *v, FILE *fp); int CopyValue (Value *dest, const Value *src); int ReadLine (void); int OpenFile (char const *fname); @@ -77,8 +81,9 @@ int JulianToGregorianOffset(int y, int m); int ParseChar (ParsePtr p, int *err, int peek); int ParseToken (ParsePtr p, DynamicBuffer *dbuf); int ParseIdentifier (ParsePtr p, DynamicBuffer *dbuf); +expr_node * ParseExpr(ParsePtr p, int *r); +void print_expr_nodes_stats(void); int EvaluateExpr (ParsePtr p, Value *v); -int Evaluate (char const **s, Var *locals, ParsePtr p); int FnPopValStack (Value *val); void Eprint (char const *fmt, ...); void Wprint (char const *fmt, ...); @@ -129,9 +134,10 @@ int StrCmpi (char const *s1, char const *s2); #endif Var *FindVar (char const *str, int create); +SysVar *FindSysVar (char const *name); int DeleteVar (char const *str); int SetVar (char const *str, Value const *val); -int GetVarValue (char const *str, Value *val, Var *locals, ParsePtr p); +int GetVarValue (char const *str, Value *val); int DoSet (Parser *p); int DoUnset (Parser *p); int DoDump (ParsePtr p); @@ -145,9 +151,10 @@ int ParseNonSpaceChar (ParsePtr p, int *err, int peek); unsigned int HashVal (char const *str); int DateOK (int y, int m, int d); Operator *FindOperator (char const *name, Operator where[], int num); -BuiltinFunc *FindFunc (char const *name, BuiltinFunc where[], int num); +BuiltinFunc *FindBuiltinFunc (char const *name); int InsertIntoSortBuffer (int dse, int tim, char const *body, int typ, int prio); void IssueSortedReminders (void); +UserFunc *FindUserFunc(char const *name); int UserFuncExists (char const *fn); void DSEToHeb (int dse, int *hy, int *hm, int *hd); int HebNameToNum (char const *mname); @@ -207,6 +214,7 @@ void set_cloexec(FILE *fp); int push_call(char const *filename, char const *func, int lineno); void clear_callstack(void); int print_callstack(FILE *fp); +int have_callstack(void); void pop_call(void); void FixSpecialType(Trigger *trig); void WriteJSONTrigger(Trigger const *t, int include_tags, int today); diff --git a/src/types.h b/src/types.h index c44c9747..c1d5b19c 100644 --- a/src/types.h +++ b/src/types.h @@ -30,21 +30,62 @@ typedef struct { int (*func)(void); } Operator; +/* New-style expr_node structure and constants */ +enum expr_node_type +{ + N_FREE, + N_ERROR, + N_CONSTANT, + N_LOCAL_VAR, + N_SHORT_VAR, + N_VARIABLE, + N_SHORT_SYSVAR, + N_SYSVAR, + N_BUILTIN_FUNC, + N_SHORT_USER_FUNC, + N_USER_FUNC, + N_BINARY_OPERATOR, + N_UNARY_OPERATOR, +}; + /* Structure for passing in Nargs and out RetVal from functions */ typedef struct { int nargs; + Value *args; Value retval; } func_info; +/* Forward reference */ +typedef struct expr_node_struct expr_node; + /* Define the type of user-functions */ typedef struct { char const *name; char minargs; char maxargs; char is_constant; + /* Old-style function calling convention */ int (*func)(func_info *); + + /* New-style function calling convention */ + int (*newfunc)(expr_node *node, Value *locals, Value *ans, int *nonconst); } BuiltinFunc; +#define SHORT_NAME_BUF 16 +typedef struct expr_node_struct { + struct expr_node_struct *child; + struct expr_node_struct *sibling; + enum expr_node_type type; + int num_kids; + union { + Value value; + int arg; + BuiltinFunc *builtin_func; + char name[SHORT_NAME_BUF]; + int (*operator_func) (struct expr_node_struct *node, Value *locals, Value *ans, int *nonconst); + } u; +} expr_node; + /* Define the structure of a variable */ typedef struct var { struct var *next; @@ -142,6 +183,8 @@ typedef Parser *ParsePtr; /* Pointer to parser structure */ #define MSF_TYPE 7 #define PASSTHRU_TYPE 8 +/* For function arguments */ +#define NO_MAX 127 /* DEFINES for debugging flags */ #define DB_PRTLINE 1 @@ -150,7 +193,7 @@ typedef Parser *ParsePtr; /* Pointer to parser structure */ #define DB_DUMP_VARS 8 #define DB_ECHO_LINE 16 #define DB_TRACE_FILES 32 -#define DB_EXPR_STACKS 64 +#define DB_PARSE_EXPR 64 /* Enumeration of the tokens */ enum TokTypes @@ -244,3 +287,26 @@ typedef struct { #define TERMINAL_BACKGROUND_UNKNOWN -1 #define TERMINAL_BACKGROUND_DARK 0 #define TERMINAL_BACKGROUND_LIGHT 1 + +typedef int (*SysVarFunc)(int, Value *); +/* The structure of a system variable */ +typedef struct { + char const *name; + char modifiable; + int type; + void *value; + int min; /* Or const-value */ + int max; +} SysVar; + +/* Define the data structure used to hold a user-defined function */ +typedef struct udf_struct { + struct udf_struct *next; + char name[VAR_NAME_LEN+1]; + expr_node *node; + char **args; + int nargs; + char const *filename; + int lineno; +} UserFunc; + diff --git a/src/userfns.c b/src/userfns.c index a267ebea..e2b7f4a3 100644 --- a/src/userfns.c +++ b/src/userfns.c @@ -30,34 +30,12 @@ #define FUNC_HASH_SIZE 32 /* Size of User-defined function hash table */ -/* Define the data structure used to hold a user-defined function */ -typedef struct udf_struct { - struct udf_struct *next; - char name[VAR_NAME_LEN+1]; - char const *text; - Var *locals; - char IsActive; - int nargs; - char const *filename; - int lineno; -} UserFunc; - /* The hash table */ static UserFunc *FuncHash[FUNC_HASH_SIZE]; -/* Access to built-in functions */ -extern int NumFuncs; -extern BuiltinFunc Func[]; - -/* We need access to the expression evaluation stack */ -extern Value ValStack[]; -extern int ValStackPtr; - static void DestroyUserFunc (UserFunc *f); static void FUnset (char const *name); static void FSet (UserFunc *f); -static int SetUpLocalVars (UserFunc *f); -static void DestroyLocalVals (UserFunc *f); /***************************************************************/ /* */ @@ -98,9 +76,11 @@ int DoFset(ParsePtr p) { int r; int c; + int i; UserFunc *func; - Var *v; - Var *local; + UserFunc *existing; + Var *locals = NULL; + Var local_array[MAX_FUNC_ARGS]; int orig_namelen; DynamicBuffer buf; @@ -125,6 +105,18 @@ int DoFset(ParsePtr p) return E_PARSE_ERR; } + /* If the function exists and was defined at the same line of the same + file, do nothing */ + existing = FindUserFunc(DBufValue(&buf)); + if (existing) { + if (!strcmp(existing->filename, FileName) && + strcmp(existing->filename, "[cmdline]") && + existing->lineno == LineNo) { + DBufFree(&buf); + /* We already have it! Our work here is done. */ + return OK; + } + } func = NEW(UserFunc); if (!func) { DBufFree(&buf); @@ -143,25 +135,22 @@ int DoFset(ParsePtr p) StrnCpy(func->name, DBufValue(&buf), VAR_NAME_LEN); DBufFree(&buf); if (!Hush) { - if (FindFunc(func->name, Func, NumFuncs)) { + if (FindBuiltinFunc(func->name)) { Eprint("%s: `%s'", ErrMsg[E_REDEF_FUNC], func->name); } } - func->locals = NULL; - func->text = NULL; - func->IsActive = 0; + func->node = NULL; func->nargs = 0; + func->args = NULL; - /* Get the local variables - we insert the local variables in REVERSE - order, but that's OK, because we pop them off the stack in reverse - order, too, so everything works out just fine. */ + /* Get the local variables */ c=ParseNonSpaceChar(p, &r, 1); if (r) return r; if (c == ')') { (void) ParseNonSpaceChar(p, &r, 0); - } - else { + } else { + locals = local_array; while(1) { if ( (r=ParseIdentifier(p, &buf)) ) return r; if (*DBufValue(&buf) == '$') { @@ -170,27 +159,24 @@ int DoFset(ParsePtr p) return E_BAD_ID; } /* If we've already seen this local variable, error */ - local = func->locals; - while(local) { - if (!StrinCmp(DBufValue(&buf), local->name, VAR_NAME_LEN)) { + for (i=0; inargs; i++) { + if (!StrinCmp(DBufValue(&buf), local_array[i].name, VAR_NAME_LEN)) { DBufFree(&buf); DestroyUserFunc(func); return E_REPEATED_ARG; } - local = local->next; } - v = NEW(Var); - if (!v) { - DBufFree(&buf); - DestroyUserFunc(func); - return E_NO_MEM; - } + i = func->nargs; + if (i >= MAX_FUNC_ARGS-1) { + DBufFree(&buf); + DestroyUserFunc(func); + return E_2MANY_ARGS; + } + local_array[i].v.type = ERR_TYPE; + StrnCpy(local_array[i].name, DBufValue(&buf), VAR_NAME_LEN); + local_array[i].next = &(local_array[i+1]); + local_array[i+1].next = NULL; func->nargs++; - v->v.type = ERR_TYPE; - StrnCpy(v->name, DBufValue(&buf), VAR_NAME_LEN); - DBufFree(&buf); - v->next = func->locals; - func->locals = v; c = ParseNonSpaceChar(p, &r, 0); if (c == ')') break; else if (c != ',') { @@ -205,7 +191,6 @@ int DoFset(ParsePtr p) if (c == '=') { (void) ParseNonSpaceChar(p, &r, 0); } - /* Copy the text over */ if (p->isnested) { Eprint("%s", ErrMsg[E_CANTNEST_FDEF]); DestroyUserFunc(func); @@ -219,10 +204,23 @@ int DoFset(ParsePtr p) DestroyUserFunc(func); return E_EOLN; } - func->text = StrDup(p->pos); - if (!func->text) { - DestroyUserFunc(func); - return E_NO_MEM; + /* Parse the expression */ + func->node = parse_expression(&(p->pos), &r, locals); + if (!func->node) { + DestroyUserFunc(func); + return r; + } + + /* Save the argument names */ + if (func->nargs) { + func->args = calloc(sizeof(char *), func->nargs); + for (i=0; inargs; i++) { + func->args[i] = StrDup(local_array[i].name); + if (!func->args[i]) { + DestroyUserFunc(func); + return E_NO_MEM; + } + } } /* If an old definition of this function exists, destroy it */ @@ -246,23 +244,22 @@ int DoFset(ParsePtr p) /***************************************************************/ static void DestroyUserFunc(UserFunc *f) { - Var *v, *prev; - - /* Free the local variables first */ - v = f->locals; - while(v) { - DestroyValue(v->v); - prev = v; - v = v->next; - free(prev); - } + int i; /* Free the function definition */ - if (f->text) free( (char *) f->text); + if (f->node) free_expr_tree(f->node); /* Free the filename */ if (f->filename) free( (char *) f->filename); + /* Free arg names */ + if (f->args) { + for (i=0; inargs; i++) { + if (f->args[i]) free(f->args[i]); + } + free(f->args); + } + /* Free the data structure itself */ free(f); } @@ -308,129 +305,17 @@ static void FSet(UserFunc *f) FuncHash[h] = f; } -/***************************************************************/ -/* */ -/* CallUserFunc */ -/* */ -/* Call a user-defined function. */ -/* */ -/***************************************************************/ -int CallUserFunc(char const *name, int nargs, ParsePtr p) +UserFunc *FindUserFunc(char const *name) { - UserFunc *f; - int h = HashVal(name) % FUNC_HASH_SIZE; - int i; - char const *s; + UserFunc *f; + int h = HashVal(name) % FUNC_HASH_SIZE; - /* Search for the function */ - f = FuncHash[h]; - while (f && StrinCmp(name, f->name, VAR_NAME_LEN)) f = f->next; - if (!f) { - Eprint("%s: `%s'", ErrMsg[E_UNDEF_FUNC], name); - return E_UNDEF_FUNC; - } - /* Debugging stuff */ - if (DebugFlag & DB_PRTEXPR) { - fprintf(ErrFp, "%s %s(", ErrMsg[E_ENTER_FUN], f->name); - for (i=0; iIsActive) { - if (DebugFlag &DB_PRTEXPR) { - fprintf(ErrFp, "%s %s() => ", ErrMsg[E_LEAVE_FUN], name); - fprintf(ErrFp, "%s\n", ErrMsg[E_RECURSIVE]); - } - return E_RECURSIVE; - } - - /* Check number of args */ - if (nargs != f->nargs) { - if (DebugFlag &DB_PRTEXPR) { - fprintf(ErrFp, "%s %s() => ", ErrMsg[E_LEAVE_FUN], name); - fprintf(ErrFp, "%s\n", - ErrMsg[(nargs < f->nargs) ? E_2FEW_ARGS : E_2MANY_ARGS]); - } - return (nargs < f->nargs) ? E_2FEW_ARGS : E_2MANY_ARGS; - } - /* Found the function - set up a local variable frame */ - h = SetUpLocalVars(f); - if (h) { - if (DebugFlag &DB_PRTEXPR) { - fprintf(ErrFp, "%s %s() => ", ErrMsg[E_LEAVE_FUN], name); - fprintf(ErrFp, "%s\n", ErrMsg[h]); - } - return h; - } - - /* Evaluate the expression */ - f->IsActive = 1; - s = f->text; - - /* Skip the opening bracket, if there's one */ - while (isempty(*s)) s++; - if (*s == BEG_OF_EXPR) { - s++; - } - push_call(f->filename, f->name, f->lineno); - h = Evaluate(&s, f->locals, p); - if (h == OK) { - pop_call(); - } - f->IsActive = 0; - DestroyLocalVals(f); - if (DebugFlag &DB_PRTEXPR) { - fprintf(ErrFp, "%s %s() => ", ErrMsg[E_LEAVE_FUN], name); - if (h) fprintf(ErrFp, "%s\n", ErrMsg[h]); - else { - PrintValue(&ValStack[ValStackPtr-1], ErrFp); - fprintf(ErrFp, "\n"); - } - } - return h; + /* Search for the function */ + f = FuncHash[h]; + while (f && StrinCmp(name, f->name, VAR_NAME_LEN)) f = f->next; + return f; } -/***************************************************************/ -/* */ -/* SetUpLocalVars */ -/* */ -/* Set up the local variables from the stack frame. */ -/* */ -/***************************************************************/ -static int SetUpLocalVars(UserFunc *f) -{ - int i, r; - Var *var; - - for (i=0, var=f->locals; var && inargs; var=var->next, i++) { - if ( (r=FnPopValStack(&(var->v))) ) { - DestroyLocalVals(f); - return r; - } - } - return OK; -} - -/***************************************************************/ -/* */ -/* DestroyLocalVals */ -/* */ -/* Destroy the values of all local variables after evaluating */ -/* the function. */ -/* */ -/***************************************************************/ -static void DestroyLocalVals(UserFunc *f) -{ - Var *v = f->locals; - - while(v) { - DestroyValue(v->v); - v = v->next; - } -} /***************************************************************/ /* */ /* UserFuncExists */ @@ -441,12 +326,33 @@ static void DestroyLocalVals(UserFunc *f) /***************************************************************/ int UserFuncExists(char const *fn) { - UserFunc *f; - int h = HashVal(fn) % FUNC_HASH_SIZE; + UserFunc *f = FindUserFunc(fn); - f = FuncHash[h]; - while (f && StrinCmp(fn, f->name, VAR_NAME_LEN)) f = f->next; if (!f) return -1; else return f->nargs; } +/***************************************************************/ +/* */ +/* UnsetAllUserFuncs */ +/* */ +/* Call FUNSET on all user funcs. Used with -ds flag to */ +/* ensure no expr_node memory leaks. */ +/* */ +/***************************************************************/ +void +UnsetAllUserFuncs(void) +{ + UserFunc *f; + UserFunc *next; + int i; + for (i=0; inext; + DestroyUserFunc(f); + f = next; + } + FuncHash[i] = NULL; + } +} diff --git a/src/utils.c b/src/utils.c index e59c6e16..72ade083 100644 --- a/src/utils.c +++ b/src/utils.c @@ -187,31 +187,33 @@ typedef struct cs_s { } cs; static cs *callstack = NULL; +static cs *freecs = NULL; static void destroy_cs(cs *entry) { - if (entry->filename) free( (void *) entry->filename); - if (entry->func) free( (void *) entry->func); - free( (void *) entry); + entry->next = freecs; + freecs = entry; } int push_call(char const *filename, char const *func, int lineno) { - cs *entry = NEW(cs); - if (!entry) { - return E_NO_MEM; + cs *entry; + if (freecs) { + entry = freecs; + freecs = freecs->next; + } else { + entry = NEW(cs); + if (!entry) { + return E_NO_MEM; + } } entry->next = NULL; - entry->filename = StrDup(filename); - entry->func = StrDup(func); + entry->filename = filename; + entry->func = func; entry->lineno = lineno; - if (!entry->filename || !entry->func) { - destroy_cs(entry); - return E_NO_MEM; - } entry->next = callstack; callstack = entry; return OK; @@ -233,11 +235,31 @@ clear_callstack(void) static void print_callstack_aux(FILE *fp, cs *entry) { - if (entry) { - print_callstack_aux(fp, entry->next); - fprintf(fp, "\n"); - (void) fprintf(fp, "%s(%d): In function `%s'", entry->filename, entry->lineno, entry->func); + int i = 0; + char const *in = "In"; + cs *prev = NULL; + while(entry) { + if (prev) { + fprintf(fp, "\n"); + in = "Called from"; + } + (void) fprintf(fp, " %s(%d): [#%d] %s function `%s'", entry->filename, entry->lineno, i, in, entry->func); + prev = entry; + entry = entry->next; + i++; + if (i > 10) { + break; + } } + if (entry) { + (void) fprintf(fp, "\n [remaining call frames omitted]"); + } +} + +int +have_callstack(void) +{ + return (callstack != NULL); } int diff --git a/src/var.c b/src/var.c index 4c65d300..a760ca7a 100644 --- a/src/var.c +++ b/src/var.c @@ -39,8 +39,6 @@ static int IntMax = INT_MAX; static Var *VHashTbl[VAR_HASH_SIZE]; -typedef int (*SysVarFunc)(int, Value *); - static double strtod_in_c_locale(char const *str, char **endptr) { @@ -511,20 +509,10 @@ int SetVar(char const *str, Value const *val) /* Get a copy of the value of the variable. */ /* */ /***************************************************************/ -int GetVarValue(char const *str, Value *val, Var *locals, ParsePtr p) +int GetVarValue(char const *str, Value *val) { Var *v; - /* Try searching local variables first */ - v = locals; - while (v) { - if (! StrinCmp(str, v->name, VAR_NAME_LEN)) - return CopyValue(val, &v->v); - v = v->next; - } - - /* Global variable... mark expression as non-constant */ - if (p) p->nonconst_expr = 1; v=FindVar(str, 0); if (!v) { @@ -785,16 +773,6 @@ int DoPreserve (Parser *p) /* */ /***************************************************************/ -/* The structure of a system variable */ -typedef struct { - char const *name; - char modifiable; - int type; - void *value; - int min; /* Or const-value */ - int max; -} SysVar; - /* Macro to access "min" but as a constval. Just to make source more readable */ #define constval min @@ -919,7 +897,6 @@ static SysVar SysVarArr[] = { }; #define NUMSYSVARS ( sizeof(SysVarArr) / sizeof(SysVar) ) -static SysVar *FindSysVar (char const *name); static void DumpSysVar (char const *name, const SysVar *v); /***************************************************************/ /* */ @@ -1016,7 +993,7 @@ int GetSysVar(char const *name, Value *val) /* Find a system var with specified name. */ /* */ /***************************************************************/ -static SysVar *FindSysVar(char const *name) +SysVar *FindSysVar(char const *name) { int top=NUMSYSVARS-1, bottom=0; int mid=(top + bottom) / 2; diff --git a/tests/ansicolors.rem b/tests/ansicolors.rem index db0ef1bb..c17be9ea 100644 --- a/tests/ansicolors.rem +++ b/tests/ansicolors.rem @@ -35,8 +35,8 @@ set a ansicolor(-1, 0, 0) set a ansicolor(42, 42, 256) set a ansicolor("foo") set a ansicolor("1 1") -set a ansicolor("-1 -1 0"); -set a ansicolor("256 1 1"); +set a ansicolor("-1 -1 0") +set a ansicolor("256 1 1") set a ansicolor(128, 128, 128, 2) set a ansicolor(128, 128, 128, -1) set a ansicolor(128, 128, 128, 0, 2)