mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-16 14:28:40 +02:00
430 lines
13 KiB
C
430 lines
13 KiB
C
/***************************************************************/
|
|
/* */
|
|
/* USERFNS.C */
|
|
/* */
|
|
/* This file contains the routines to support user-defined */
|
|
/* functions. */
|
|
/* */
|
|
/* This file is part of REMIND. */
|
|
/* Copyright (C) 1992-2024 by Dianne Skoll */
|
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/* */
|
|
/***************************************************************/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include <stdlib.h>
|
|
#include "types.h"
|
|
#include "globals.h"
|
|
#include "protos.h"
|
|
#include "err.h"
|
|
#include "expr.h"
|
|
|
|
#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);
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoFunset */
|
|
/* */
|
|
/* Undefine a user-defined function - the FUNSET command. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoFunset(ParsePtr p)
|
|
{
|
|
int r;
|
|
int seen_one = 0;
|
|
DynamicBuffer buf;
|
|
DBufInit(&buf);
|
|
|
|
while(1) {
|
|
r = ParseIdentifier(p, &buf);
|
|
if (r == E_EOLN) {
|
|
DBufFree(&buf);
|
|
break;
|
|
}
|
|
seen_one = 1;
|
|
FUnset(DBufValue(&buf));
|
|
DBufFree(&buf);
|
|
}
|
|
if (seen_one) return OK;
|
|
return E_PARSE_ERR;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DoFset */
|
|
/* */
|
|
/* Define a user-defined function - the FSET command. */
|
|
/* */
|
|
/***************************************************************/
|
|
int DoFset(ParsePtr p)
|
|
{
|
|
int r;
|
|
int c;
|
|
UserFunc *func;
|
|
Var *v;
|
|
int orig_namelen;
|
|
|
|
DynamicBuffer buf;
|
|
DBufInit(&buf);
|
|
|
|
/* Get the function name */
|
|
if ( (r=ParseIdentifier(p, &buf)) ) return r;
|
|
if (*DBufValue(&buf) == '$') {
|
|
DBufFree(&buf);
|
|
return E_BAD_ID;
|
|
}
|
|
orig_namelen = buf.len;
|
|
|
|
/* Should be followed by '(' */
|
|
c = ParseNonSpaceChar(p, &r, 0);
|
|
if (r) {
|
|
DBufFree(&buf);
|
|
return r;
|
|
}
|
|
if (c != '(') {
|
|
DBufFree(&buf);
|
|
return E_PARSE_ERR;
|
|
}
|
|
|
|
func = NEW(UserFunc);
|
|
if (!func) {
|
|
DBufFree(&buf);
|
|
return E_NO_MEM;
|
|
}
|
|
if (FileName) {
|
|
func->filename = StrDup(FileName);
|
|
} else {
|
|
func->filename = StrDup("[cmdline]");
|
|
}
|
|
if (!func->filename) {
|
|
free(func);
|
|
return E_NO_MEM;
|
|
}
|
|
func->lineno = LineNo;
|
|
StrnCpy(func->name, DBufValue(&buf), VAR_NAME_LEN);
|
|
DBufFree(&buf);
|
|
if (!Hush) {
|
|
if (FindFunc(func->name, Func, NumFuncs)) {
|
|
Eprint("%s: `%s'", ErrMsg[E_REDEF_FUNC], func->name);
|
|
}
|
|
}
|
|
func->locals = NULL;
|
|
func->text = NULL;
|
|
func->IsActive = 0;
|
|
func->nargs = 0;
|
|
|
|
/* 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. */
|
|
|
|
c=ParseNonSpaceChar(p, &r, 1);
|
|
if (r) return r;
|
|
if (c == ')') {
|
|
(void) ParseNonSpaceChar(p, &r, 0);
|
|
}
|
|
else {
|
|
while(1) {
|
|
if ( (r=ParseIdentifier(p, &buf)) ) return r;
|
|
if (*DBufValue(&buf) == '$') {
|
|
DBufFree(&buf);
|
|
DestroyUserFunc(func);
|
|
return E_BAD_ID;
|
|
}
|
|
v = NEW(Var);
|
|
if (!v) {
|
|
DBufFree(&buf);
|
|
DestroyUserFunc(func);
|
|
return E_NO_MEM;
|
|
}
|
|
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 != ',') {
|
|
DestroyUserFunc(func);
|
|
return E_PARSE_ERR;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Allow an optional = sign: FSET f(x) = x*x */
|
|
c = ParseNonSpaceChar(p, &r, 1);
|
|
if (c == '=') {
|
|
(void) ParseNonSpaceChar(p, &r, 0);
|
|
}
|
|
/* Copy the text over */
|
|
if (p->isnested) {
|
|
Eprint("%s", ErrMsg[E_CANTNEST_FDEF]);
|
|
DestroyUserFunc(func);
|
|
return E_PARSE_ERR;
|
|
}
|
|
|
|
func->text = StrDup(p->pos);
|
|
if (!func->text) {
|
|
DestroyUserFunc(func);
|
|
return E_NO_MEM;
|
|
}
|
|
|
|
/* If an old definition of this function exists, destroy it */
|
|
FUnset(func->name);
|
|
|
|
/* Add the function definition */
|
|
FSet(func);
|
|
if (orig_namelen > VAR_NAME_LEN) {
|
|
Wprint("Warning: Function name `%s...' truncated to `%s'",
|
|
func->name, func->name);
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* DestroyUserFunc */
|
|
/* */
|
|
/* Free up all the resources used by a user-defined function. */
|
|
/* */
|
|
/***************************************************************/
|
|
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);
|
|
}
|
|
|
|
/* Free the function definition */
|
|
if (f->text) free( (char *) f->text);
|
|
|
|
/* Free the filename */
|
|
if (f->filename) free( (char *) f->filename);
|
|
|
|
/* Free the data structure itself */
|
|
free(f);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* FUnset */
|
|
/* */
|
|
/* Delete the function definition with the given name, if */
|
|
/* it exists. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void FUnset(char const *name)
|
|
{
|
|
UserFunc *cur, *prev;
|
|
int h;
|
|
|
|
h = HashVal(name) % FUNC_HASH_SIZE;
|
|
|
|
cur = FuncHash[h];
|
|
prev = NULL;
|
|
while(cur) {
|
|
if (! StrinCmp(name, cur->name, VAR_NAME_LEN)) break;
|
|
prev = cur;
|
|
cur = cur->next;
|
|
}
|
|
if (!cur) return;
|
|
if (prev) prev->next = cur->next; else FuncHash[h] = cur->next;
|
|
DestroyUserFunc(cur);
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* FSet */
|
|
/* */
|
|
/* Insert a user-defined function into the hash table. */
|
|
/* */
|
|
/***************************************************************/
|
|
static void FSet(UserFunc *f)
|
|
{
|
|
int h = HashVal(f->name) % FUNC_HASH_SIZE;
|
|
f->next = FuncHash[h];
|
|
FuncHash[h] = f;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* CallUserFunc */
|
|
/* */
|
|
/* Call a user-defined function. */
|
|
/* */
|
|
/***************************************************************/
|
|
int CallUserFunc(char const *name, int nargs, ParsePtr p)
|
|
{
|
|
UserFunc *f;
|
|
int h = HashVal(name) % FUNC_HASH_SIZE;
|
|
int i;
|
|
char const *s;
|
|
|
|
/* 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; i<nargs; i++) {
|
|
PrintValue(&ValStack[ValStackPtr - nargs + i], ErrFp);
|
|
if (i<nargs-1) fprintf(ErrFp, ", ");
|
|
}
|
|
fprintf(ErrFp, ")\n");
|
|
}
|
|
/* Detect illegal recursive call */
|
|
if (f->IsActive) {
|
|
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;
|
|
}
|
|
|
|
/***************************************************************/
|
|
/* */
|
|
/* 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 && i<f->nargs; 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 */
|
|
/* */
|
|
/* Return the number of arguments accepted by the function if */
|
|
/* it is defined, or -1 if it is not defined. */
|
|
/* */
|
|
/***************************************************************/
|
|
int UserFuncExists(char const *fn)
|
|
{
|
|
UserFunc *f;
|
|
int h = HashVal(fn) % FUNC_HASH_SIZE;
|
|
|
|
f = FuncHash[h];
|
|
while (f && StrinCmp(fn, f->name, VAR_NAME_LEN)) f = f->next;
|
|
if (!f) return -1;
|
|
else return f->nargs;
|
|
}
|
|
|