ftp.nice.ch/pub/next/graphics/3d/NXplot3d.3.2.NIHS.bs.tar.gz#/NXplot3d.3.2/Source/Expression.m

This is Expression.m in view mode; [Download] [Up]

/*
    Expression.m

    The Expression class is implemented using a grammar generated by the Unix
    program yacc, and a lexical scanner generated by the Unix program lex.
    The only interface between these parsing modules and this class is the
    function _EXPParseExpression, which actually performs the parse.  The
    results of the parse are returned in two data structures: a parse tree
    representing the expression and a hashtable which holds that variables
    found in the expression.

    The parse tree's nodes are Term structs (declared in exprDefs.h).  There
    is a node for each piece of the expression.  For example, when the
    expression "A+5" is parsed, three nodes result.  The top node of the tree
    represents the binary operator "+".  This top node has two subnodes.  The
    first subnode represents the variable "A".  The second represents the
    integer constant "5".  Since variables can appear more than once in an
    expression, their nodes may occur more than once in the parse tree.
    
    A parse tree is evaluated by doing a depth first traversal of the tree,
    bubbling up the results of each sub-tree until the final value rises
    to the top.

    You may freely copy, distribute, and reuse the code in this example.
    NeXT disclaims any warranty of any kind, expressed or implied, as to its
    fitness for any particular use.
*/

#import "Graph.h"

/* declaration of methods static to this class */
@interface Expression(ExpressionPrivate)
- (int)_updateResults;
- (EXPTermPtr)_addVarTerm:(const char *)name;
@end

@implementation Expression

/* function which can be applied to parse tree nodes with applyToTerms() */
typedef void ParseTreeFunc(void *info, Term *term);

static void freeContents(Expression *self);
static void applyToTerms(Term *t, ParseTreeFunc *func, void *data, int mask);
static void updateTerm(void *data, Term *t);
static float evalTerm(Term *t, int *indices);
static Term *termOfVar(Expression *self, const char *varName);
static NXHashTable *makeBuiltInFuncTable(NXZone *zone);
static NXHashTable *getBuiltInFuncs(void);
static Function *addFuncTerm(NXHashTable *table, const char *name, int min, int max, EXPTermEvalFunc *func);
static void safeFree(void **data);
static EXPTermEvalFunc sinStub, cosStub, tanStub;
static EXPTermEvalFunc asinStub, acosStub, atanStub;
static EXPTermEvalFunc expStub, lnStub, sqrtStub, sumStub;
static EXPTermEvalFunc powStub,log10Stub,jnStub,ynStub;
static EXPTermEvalFunc absStub,floorStub,ceilStub,YlmStub;
static char *termName(const Term *t);
static void FunctionFree(const void *info, void *data);
static unsigned	VarTermHash(const void *info, const void *data);
static int VarTermCompare(const void *info, const void *data1, const void *data2);

/*
 * Shared table of built in functions.  All Expressions which haven't had
 * any application functions added to them shared this table, which contains
 * just the built in functions.
 */
static NXHashTable *BuiltInFuncTable = NULL;

/* data for a built in function */
typedef struct _BuiltInFunc {
    const char *name;		/* name of the function */
    EXPTermEvalFunc *func;	/* proc to call for evaluation */
    int minArgs;
    int maxArgs;
} BuiltInFunc;

/* table of built in functions */
static const BuiltInFunc FuncList[] = {
	{"sin", &sinStub, 1, 1},
	{"cos", &cosStub, 1, 1},
	{"tan", &tanStub, 1, 1},
	{"asin", &asinStub, 1, 1},
	{"acos", &acosStub, 1, 1},
	{"atan", &atanStub, 1, 1},
	{"exp", &expStub, 1, 1},
	{"ln", &lnStub, 1, 1},
	{"log", &lnStub, 1, 1},
	{"sqrt", &sqrtStub, 1, 1},
	{"sum", &sumStub, 1, -1},
	{"pow", &powStub, 2, 2},
	{"log10",&log10Stub,1,1},
	{"jn",&jnStub,2,2},
	{"yn",&ynStub,2,2},
	{"ylm",&YlmStub,4,4}, 
	{"floor",&floorStub,1,1},
	{"ceil",&ceilStub,1,1},
	{"abs",&absStub,1,1}
};

#define NUM_BUILTIN_FUNCS	(sizeof(FuncList)/sizeof(BuiltInFunc))

/* prototype used to create hashtables of variable terms */
static NXHashTablePrototype VarTermProto = {&VarTermHash, &VarTermCompare, (void (*)(const void *info, void *data))&_EXPFreeTerm, 0};

- init {
    [super init];
    resolution = 1;
    dimensions = 1;
    varTerms = NXCreateHashTableFromZone(VarTermProto, 0, NULL, [self zone]);
    return self;
}

- free {
    freeContents(self);
  /* if we have a function table and its not the shared one */
    if (validFuncs && validFuncs != BuiltInFuncTable)
	NXFreeHashTable(validFuncs);
    return [super free];
}

- (BOOL)parse:(const char *)expressionString {
    NXZone *zone;

    zone = [self zone];
  /* clear away any results from a previous parse */
    freeContents(self);
    varTerms = NXCreateHashTableFromZone(VarTermProto, 0, NULL, zone);
    resultsValid = NO;
    text = NXCopyStringBufferFromZone(expressionString, zone);
    if (!validFuncs)
	validFuncs = getBuiltInFuncs();
    return _EXPParseExpression(text, validFuncs, &parseTree, varTerms, zone);
}

- (const char *)text {
    return text;
}

- setResolution:(int)count {
    if (resolution != count) {
      /*
       * Changing the resolution means we have to recalculate next time
       * we're asked for results
       */
	resultsValid = NO;
	resolution = count;
    }
    return self;
}

- (int)resolution {
    return resolution;
}

- setVar:(const char *)varName value:(float)val {
    Term *t;

    t = termOfVar(self, varName);
    if (!t)
	t = [self _addVarTerm:varName];
  /*
   * If the term was previously a vector term, we change it to a variable
   * term (one that has a single value).
   */
    if (t->tag == vectorTerm) {
	t->tag = varTerm;
	safeFree((void **)&t->data.vector.vals);
	t->data.var.name = t->data.vector.name;
	resultsValid = NO;
    }
    NX_ASSERT(t->tag == varTerm, "Invalid term type in setVar:value:");
    if (t->data.var.val != val) {
	t->data.var.val = val;
	resultsValid = NO;		/* must recalc after a var is set */
    }
    return self;
}

- (float)varValue:(const char *)varName {
    Term *t;

    t = termOfVar(self, varName);
    if (t) {
	if (t->tag == varTerm)
	    return t->data.var.val;
	else
	    NX_RAISE(expErrInvalidVarType, self, (void *)varName);
    } else
	NX_RAISE(expErrInvalidVarName, self, (void *)varName);
}

- setVar:(const char *)varName vector:(float *)vals numVals:(int)count {
    Term *t;

    resultsValid = NO;
    t = termOfVar(self, varName);
    if (!t)
	t = [self _addVarTerm:varName];
  /*
   * If the term was previously a non-vector variable, we change it to a
   * vector variable term.
   */
    if (t->tag == varTerm) {
	t->tag = vectorTerm;
	t->data.vector.name = t->data.var.name;
	t->data.vector.hasRange = NO;
	t->data.vector.vals = NULL;
	t->data.vector.resolution = 0;
	t->data.vector.dimension = 0;
    }
    NX_ASSERT(t->tag == vectorTerm, "Invalid term type in setVar:vector:");
    safeFree((void **)&t->data.vector.vals);
    t->data.vector.resolution = count;
    t->data.vector.vals = vals;
    t->data.vector.changed = YES;
    resolution = count;
    return self;
}

- varVector:(const char *)varName vector:(float **)vals numVals:(int *)count {
    Term *t;

    t = termOfVar(self, varName);
    if (t) {
	if (t->tag == vectorTerm) {
	    updateTerm(self, t);
	    *count = t->data.vector.resolution;
	    *vals = t->data.vector.vals;
	} else
	    NX_RAISE(expErrInvalidVarType, self, (void *)varName);
    } else
    	NX_RAISE(expErrInvalidVarName, self, (void *)varName);
    return self;
}

- setVar:(const char *)varName min:(float)minVal max:(float)maxVal {
    Term *t;

    if (minVal > maxVal)
	NX_RAISE(expErrMinMax, self, NULL);
    t = termOfVar(self, varName);
    if (!t)
	t = [self _addVarTerm:varName];
  /*
   * If the term was previously a non-vector variable, we change it to a
   * vector variable term.
   */
    if (t->tag == varTerm) {
	t->tag = vectorTerm;
	t->data.vector.name = t->data.var.name;
	t->data.vector.hasRange = YES;
	t->data.vector.vals = NULL;
	t->data.vector.changed = YES;
	t->data.vector.dimension = 0;
    }
    NX_ASSERT(t->tag == vectorTerm, "Invalid term type in setVar:min:max:");

  /*
   * We optimize and do nothing if the passed values are the same as our
   * current values.
   */
    if (t->data.vector.changed || t->data.vector.min != minVal || t->data.vector.max != maxVal) {
	safeFree((void **)&t->data.vector.vals);
	t->data.vector.resolution = 0;
	t->data.vector.min = minVal;
	t->data.vector.max = maxVal;
	t->data.vector.changed = YES;
	resultsValid = NO;
    }    
    return self;
}

- var:(const char *)varName min:(float *)minVal max:(float *)maxVal {
    Term *t;

    t = termOfVar(self, varName);
    if (t) {
	if (t->tag == vectorTerm) {
	    updateTerm(self, t);
	    *minVal = t->data.vector.min;
	    *maxVal = t->data.vector.max;
	} else
	    NX_RAISE(expErrInvalidVarType, self, (void *)varName);
    } else
    	NX_RAISE(expErrInvalidVarName, self, (void *)varName);
    return self;
}

- setVar:(const char *)varName dimension:(short)dimensionNum {
    Term *t;

    if (dimensionNum < 0 || dimensionNum >= dimensions)
	NX_RAISE(expInvalidDimension, self, (void *)varName);
    resultsValid = NO;
    t = termOfVar(self, varName);
    if (!t)
	t = [self _addVarTerm:varName];
  /*
   * If the term was previously a non-vector variable, we change it to a
   * vector variable term.
   */
    if (t->tag == varTerm) {
	t->tag = vectorTerm;
	t->data.vector.name = t->data.var.name;
	t->data.vector.hasRange = YES;
	t->data.vector.vals = NULL;
	t->data.vector.changed = YES;
	t->data.vector.dimension = 0;
    }
    NX_ASSERT(t->tag == vectorTerm, "Invalid term type in setVar:dimension:");
    t->data.vector.dimension = dimensionNum;
    return self;
}

- var:(const char *)varName dimension:(short *)dimensionNum {
    Term *t;

    t = termOfVar(self, varName);
    if (t) {
	if (t->tag == vectorTerm)
	    *dimensionNum = t->data.vector.dimension;
	else
	    NX_RAISE(expErrInvalidVarType, self, (void *)varName);
    } else
    	NX_RAISE(expErrInvalidVarName, self, (void *)varName);
    return self;
}

- (float)resultValue {
    [self _updateResults];
    return *results;
}

- resultsVector:(float **)vals numVals:(int *)count {
    *count = [self _updateResults];
    *vals = results;
    return self;
}

- resultsMin:(float *)minVal max:(float *)maxVal {
    [self _updateResults];
    *minVal = resultsMin;
    *maxVal = resultsMax;
    return self;
}

- setDimensions:(short)count {
    if (dimensions != count)
	resultsValid = NO;
    dimensions = count;
    return self;
}

- (short)dimensions {
    return dimensions;
}

/*
 * Since the varables are all in a NXHashTable, we just use the NXHashTable
 * functions to enumerate through the names of the variables.
 */

- (EXPEnumState)beginVariableEnumeration {
    NXHashState *state;

    state = NXZoneMalloc([self zone], sizeof(NXHashState));
    *state = NXInitHashState(varTerms);
    return state;
}

- (const char *)nextVariable:(EXPEnumState)state {
    const Term *t;

    if (NXNextHashState(varTerms, (NXHashState *)state, (void **)&t))
	return termName(t);
    else
	return NULL;
}

- (void)endVariableEnumeration:(EXPEnumState)state {
    free(state);
}

- addFuncTerm:(const char *)name minArgs:(int)min maxArgs:(int)max
					evalFunc:(EXPTermEvalFunc *)func {
    Function *existingType;

  /*
   * If we dont have a function table yet, get one.  If we do have one, but
   * its the shared built in table, get a new one that we can safely modify.
   */
    if (!validFuncs || validFuncs == BuiltInFuncTable)
	validFuncs = makeBuiltInFuncTable([self zone]);
    existingType = addFuncTerm(validFuncs, name, min, max, func);
    if (existingType)
	NX_RAISE(expFuncTypeInUse, self, existingType);
    return self;
}

- removeFuncTerm:(const char *)name {
    Function *realFunc;
    Function key;

    if (validFuncs) {
      /* look up the func term by name in our table of functions */
	key.name = (char *)name;
	if (realFunc = NXHashGet(validFuncs, &key)) {
	  /*
	   * If we are using the shared table of built ins, get a new table
	   * that we can safely modify (this covers the case of someone
	   * wishing to remove a built in function, possibly to redefine it).
	   */
	    if (validFuncs == BuiltInFuncTable) {
		validFuncs = makeBuiltInFuncTable([self zone]);
		realFunc = NXHashGet(validFuncs, &key);
	    }
	    NXHashRemove(validFuncs, realFunc);
	    free(realFunc);
	    return self;
	}
    }
    return self;
}

/*
 * This is a utililty routine that recursively applies a function to all
 * terms of a parse tree. data is a blind pointer that is simply passed
 * along through the function call.  The function is only called on terms
 * whose type match the given mask.
 */
static void applyToTerms(Term *t, ParseTreeFunc *func, void *data, int mask) {
    int i;
    Term **tPtr;

    for (i = t->numSubterms, tPtr = t->subterms; i--; tPtr++)
	applyToTerms(*tPtr, func, data, mask);
    if (t->tag & mask)
	(*func)(data, t);
}

/*
 * Empties the contents of the Expression.  Since the variable terms can
 * exist multiple times in the tree, we first run through the tree and
 * free all the nodes except that variable nodes.  Then we free the hash
 * table of variable terms, including the terms themselves. 
 */
static void freeContents(Expression *self) {
    safeFree((void **)&self->text);
  /* free the non-variable terms */
    if (self->parseTree)
	applyToTerms(self->parseTree, _EXPFreeTerm, NULL, ~(varTerm|vectorTerm));
  /* free the shared variable terms */
    NXFreeHashTable(self->varTerms);
    safeFree((void **)&self->results);
}

/*
 * Allocates a new term of the given type, with room for subterms.  The
 * subterms themselves follow as a variable number of arguments.  The are
 * copied into the subterms list of the new term.
 */
Term *_EXPAllocTerm(NXZone *zone, TermTag tag, int numSubterms, ...) {
    Term *t;
    int i;
    va_list args;

    t = NXZoneCalloc(zone, sizeof(Term) + (numSubterms-1) * sizeof(Term *), 1);
    t->tag = tag;
    t->numSubterms = numSubterms;
    va_start(args, numSubterms);
    for (i = 0; i < numSubterms; i++)
	t->subterms[i] = va_arg(args, Term *);
    va_end(args);
    return t;
}

/*
 * Frees a term and any associated data.  This routine can be used as the
 * free function of a NXHashTable prototype, or as a proc passed to
 * applyToTerms().
 */
void _EXPFreeTerm(void *info, Term *data) {
    Term *t = (Term *)data;

    if (t->tag == vectorTerm) {
	free(t->data.vector.vals);
	free(t->data.vector.name);
    } else if (t->tag == varTerm)
	free(t->data.var.name);
    free(t);
}

/*
 * Makes sure a term is up to date.  Since terms recalculate any internal
 * state lazily, this must be called before making use of a term's value.
 * We apply this function recursively to all terms before evaluating an
 * Expression, and apply to any term if we are asked to return the values
 * held within the term (for example, from -varVector:vector:numVals:).
 */
static void updateTerm(void *data, Term *t) {
    Expression *self = data;

    if (t->tag != vectorTerm)
	return;		/* only vector terms require work to stay up to date */

  /* Ensure this term has the same resolution as the rest of the Expression. */ 
    if (self->resolution != t->data.vector.resolution) {
      /*
       * We can change its resolution if we interpolate values for this term
       * within a range.  Else if we were passed a list of values for this
       * term, its an exception if the resolution is no longer in sync.
       */
	if (t->data.vector.hasRange) {
	    safeFree((void **)&t->data.vector.vals);
	    t->data.vector.vals = NXZoneMalloc([self zone],
					self->resolution * sizeof(float));
	    t->data.vector.resolution = self->resolution;
	    t->data.vector.changed = YES;    /* remember to re-interpolate */
	} else
	    NX_RAISE(expErrResolutionMismatch, self,
					(void *)t->data.vector.name);
    }

    if (t->data.vector.changed) {
	if (t->data.vector.hasRange) {
	  /* interpolate a list of values between min and max */
	    int i;
	    float delta;
	    float *val, *prevVal;

	    i = self->resolution - 1;
	    if (i) {
		delta = (t->data.vector.max - t->data.vector.min) / i;
		prevVal = t->data.vector.vals;
		*prevVal = t->data.vector.min;
		val = prevVal + 1;
		while (i--)
		    *val++ = *prevVal++ + delta;
		*(val-1) = t->data.vector.max;	/* to be sure we hit max */
	    } else
		*t->data.vector.vals = t->data.vector.min;
	} else {
	  /* scan the list of values passed in to find the min and max */
	    int i;
	    float newMin, newMax;
	    float *val;

	    val = t->data.vector.vals;
	    newMin = newMax = *val++;
	    for (i = t->data.vector.resolution; i--; val++) {
		if (*val > newMax)
		    newMax = *val;
		else if (*val < newMin)
		    newMin = *val;
	    }
	    t->data.vector.min = newMin;
	    t->data.vector.max = newMax;
	}
	t->data.vector.changed = NO;	/* we're now up to date */
    }
}

/*
 * Ensures that the results calculated for this Expression are up to date.
 * We first make sure all the terms in the parse tree are up to date by
 * applying updateTerm() to all of them.  We then evaluate the Expression
 * for every n times, depending on the resolution, storing all the results
 * and calculating their min and max. 
 */
- (int)_updateResults {
#define MAX_INDICES 10
    int i, j;
    float *f;
    int indicesBuffer[MAX_INDICES];
    int *indices = indicesBuffer;
    int totalVals;
    NXZone *zone = [self zone];

    totalVals = resolution;
    for (i = 1; i < dimensions; i++)
	totalVals *= resolution;
    if (!resultsValid) {
	if (parseTree) {
	    applyToTerms(self->parseTree, updateTerm, self, -1);
	    safeFree((void **)&results);
	    results = NXZoneMalloc(zone, sizeof(float) * totalVals);
	    if (dimensions > MAX_INDICES)
		indices = NXZoneMalloc(zone, sizeof(int) * dimensions);
	    bzero(indices, sizeof(int) * dimensions);
	    *results = evalTerm(parseTree, indices);
	    resultsMin = *results;
	    resultsMax = *results;
	    for (i = 1, f = results + 1; i < totalVals; i++, f++) {

	      /*
	       * Increment the indices.  The first one always changes.  The
	       * others change only when the previous one wraps around (like
	       * an odometer with base = resolution).
	       */
		for (j = 0; j < dimensions; j++) {
		    indices[j] = (indices[j] + 1) % resolution;
		    if (indices[j])
			break;
		}

	      /*
	       * We pass the loop indices down through the evaluation recursion
	       * so vector terms can know which element of their vectors they
	       * should use for this evaluation.
	       */
		*f = evalTerm(parseTree, indices);
		if (*f > resultsMax)
		    resultsMax = *f;
		else if (*f < resultsMin)
		    resultsMin = *f;
	    }
	    if (dimensions > MAX_INDICES)
		NXZoneFree(zone, indices);
	} else
	    NX_RAISE(expErrNoText, self, NULL);
	resultsValid = YES;
    }
    return totalVals;
}

#define MAX_ARGS	50

/*
 * Evaluates a particular term.  In order to evaluate itself, any term with
 * subterms must recursively evaluate those first.
 */
static float evalTerm(Term *t, int *indices) {
    float result = 0.0;		/* initted to quiet -Wall */
    float base;
    float argsBuffer[MAX_ARGS];
    float *args;
    int i;

    switch (t->tag) {
	case constantTerm:
	    NX_ASSERT(t->numSubterms == 0, "Wrong #subterms in evalTerm");
	    result = t->data.constant.val;
	    break;
	case varTerm:
	    NX_ASSERT(t->numSubterms == 0, "Wrong #subterms in evalTerm");
	    result = t->data.var.val;
	    break;
	case vectorTerm:
	    NX_ASSERT(t->numSubterms == 0, "Wrong #subterms in evalTerm");
	    result = t->data.vector.vals[indices[t->data.vector.dimension]];
	    break;
	case binOpTerm:
	    NX_ASSERT(t->numSubterms == 2 ||
			(t->data.binOp.op == '-' && t->numSubterms == 1),
					"Wrong #subterms in evalTerm");
	    switch (t->data.binOp.op) {
		case '+':
		    result = evalTerm(t->subterms[0], indices) +
				evalTerm(t->subterms[1], indices);
		    break;
		case '-':
		    if (t->numSubterms == 2)
			result = evalTerm(t->subterms[0], indices) -
					evalTerm(t->subterms[1], indices);
		    else
			result = - evalTerm(t->subterms[0], indices);
		    break;
		case '*':
		    result = evalTerm(t->subterms[0], indices) *
				evalTerm(t->subterms[1], indices);
		    break;
		case '/':
		    result = evalTerm(t->subterms[0], indices) /
				evalTerm(t->subterms[1], indices);
		    break;
		case '%':
		    result = (int)rint(evalTerm(t->subterms[0], indices)) %
				(int)rint(evalTerm(t->subterms[1], indices));
		    break;
		case '^':
		  /* optimize for raising to an integral power */
		    if (t->subterms[1]->tag == constantTerm &&
				t->subterms[1]->data.constant.isInt &&
				t->subterms[1]->data.constant.val >= 1) {
			result = base = evalTerm(t->subterms[0], indices);
			for (i = t->subterms[1]->data.constant.val; --i; )
			    result *= base;
		    } else
			result = pow(evalTerm(t->subterms[0], indices),
					evalTerm(t->subterms[1], indices));
		    break;
		default:
		    NX_ASSERT(FALSE, "Unknown binary op type in evalTerm");
	    }
	    break;
	case funcTerm:
	  /*
	   * For functions, we first ensure we have a large enough buffer
	   * for the values of all the arguments.  If there are few enough
	   * arguments, we use a buffer on the stack instead of thrashing
	   * the heap.  We then buffer up all the results of evaluating
	   * the arguments, and then pass this array of argument values
	   * to the proc that we use to evaluate this type of function.
	   */
	    if (t->numSubterms > MAX_ARGS)
		args = NXZoneMalloc(NXDefaultMallocZone(),
					sizeof(float) * t->numSubterms);
	    else
		args = argsBuffer;
	    for (i = 0; i < t->numSubterms; i++)
		args[i] = evalTerm(t->subterms[i], indices);
	    result = t->data.func.type->evalFunc(t->numSubterms, args);
    	    if (t->numSubterms > MAX_ARGS)
		NXZoneFree(NXDefaultMallocZone(), args);
	    break;
	default:
	    NX_ASSERT(FALSE, "Invalid term type in evalTerm");
    }
    return result;
}

/* Utility routine to look up a variable by name in the variable hashtable. */
static Term *termOfVar(Expression *self, const char *varName) {
    Term key;

    if (self->parseTree) {
	key.tag = varTerm;
	key.data.var.name = (char *)varName;
	return NXHashGet(self->varTerms, &key);
    } else
	NX_RAISE(expErrNoText, self, NULL);
}

/* adds a variable term to the Expressions hashtable of them */
- (Term *)_addVarTerm:(const char *)name {
    Term *newTerm;
    Term *existingTerm;

    newTerm = _EXPAllocTerm([self zone], varTerm, 0);
    newTerm->data.var.name = NXCopyStringBufferFromZone(name, [self zone]);
    existingTerm = NXHashInsertIfAbsent(varTerms, newTerm);
    NX_ASSERT(existingTerm == newTerm, "_addVarTerm: called with existing term");
    return newTerm;
}

/* frees some storage, NULL'ing out the pointer */
static void safeFree(void **data) {
    free(*data);
    *data = NULL;
}

/* free function used in the NXHashTable prototype for functions */
static void FunctionFree(const void *info, void *data) {
    free(((Function *)data)->name);
    free(data);
}

/*
 * Adds a func term to a HashTable of them.  Returns any existing entry
 * with the same name, else NULL.
 */
static Function *addFuncTerm(NXHashTable *table, const char *name, int min, int max, EXPTermEvalFunc *func) {
    Function *newFunc;
    Function *existingType;

    newFunc = NXZoneMalloc(NXZoneFromPtr(table), sizeof(Function));
    newFunc->name = NXCopyStringBufferFromZone(name, NXZoneFromPtr(table));
    newFunc->minArgs = min;
    newFunc->maxArgs = max;
    newFunc->evalFunc = func;
    existingType = NXHashInsertIfAbsent(table, newFunc);
    if (existingType != newFunc)
	return existingType;
    else
	return NULL;
}

/*
 * Returns a global table of all built in functions.  This table is shared
 * by expressions that dont have application functions added to them.
 */
static NXHashTable *getBuiltInFuncs(void) {
    if (!BuiltInFuncTable)
	BuiltInFuncTable = makeBuiltInFuncTable(NXDefaultMallocZone());
    return BuiltInFuncTable;
}

/* Returns a new hashtable of all built in functions. */
static NXHashTable *makeBuiltInFuncTable(NXZone *zone) {
    NXHashTable *table;
    NXHashTablePrototype FuncTermProto;
    const BuiltInFunc *bif;
    int i;

    FuncTermProto = NXStrStructKeyPrototype;
    FuncTermProto.free = FunctionFree;
    table = NXCreateHashTableFromZone(FuncTermProto, NUM_BUILTIN_FUNCS,
								NULL, zone);
    for (i = NUM_BUILTIN_FUNCS, bif = FuncList; i--; bif++)
	(void)addFuncTerm(table, bif->name, bif->minArgs, bif->maxArgs, bif->func);
    return table;
}

/* Returns the name of a term. */
static char *termName(const Term *t) {
    switch (t->tag) {
	case varTerm:
	    return t->data.var.name;
	case vectorTerm:
	    return t->data.vector.name;
	default:
	    NX_ASSERT(FALSE, "Bogus term type in VarTermHash");
	    return NULL;
    }
}

/* hashing function for variable terms.  Used in hashtable prototypes. */
static unsigned	VarTermHash(const void *info, const void *data) {
    return NXStrHash(info, termName(data));
}

/* comparison function for variable terms.  Used in hashtable prototypes. */
static int VarTermCompare(const void *info, const void *data1, const void *data2) {
    return NXStrIsEqual(info, termName(data1), termName(data2));
}

@end

/* These procs implement the built in functions */

static float sinStub(int numArgs, float *arg)	{ return sin(*arg); }
static float cosStub(int numArgs, float *arg)	{ return cos(*arg); }
static float tanStub(int numArgs, float *arg)	{ return tan(*arg); }
static float asinStub(int numArgs, float *arg)	{ return asin(*arg); }
static float acosStub(int numArgs, float *arg)	{ return acos(*arg); }
static float atanStub(int numArgs, float *arg)	{ return atan(*arg); }
static float expStub(int numArgs, float *arg)	{ return exp(*arg); }
static float lnStub(int numArgs, float *arg)	{ return log(*arg); }
static float sqrtStub(int numArgs, float *arg)	{ return sqrt(*arg); }

static float sumStub(int numArgs, float *arg) {
    float sum = 0.0;

    while (numArgs--)
	sum += *arg++;
    return sum;
}

/* added 2/94  Steve Ludtke */
static float powStub(int numArgs, float *arg) 	{ return pow(arg[0],arg[1]); }
static float log10Stub(int numArgs, float *arg) { return log10(arg[0]); }
static float jnStub(int numArgs, float *arg) 	
	{ return jn((int)arg[0],arg[1]); }
static float ynStub(int numArgs, float *arg) 	
	{ return yn((int)arg[0],arg[1]); }
static float absStub(int numArgs, float *arg)	{ return fabs(arg[0]); }
static float floorStub(int numArgs, float *arg)	{ return floor(arg[0]); }
static float ceilStub(int numArgs, float *arg)	{ return ceil(arg[0]); }


static float YlmStub(int numArgs, float *arg) 
{
int l,m;
float theta,phi;

if (numArgs!=4) return(0.0);
l=(int)arg[0];
m=(int)arg[1];
theta=arg[2];
phi=arg[3];

switch(l) {
	case 0:
		if (m==0) return(sqrt(1.0/(4.0*M_PI)));
		break;
	case 1:
		if (m==-1) return(1.0/2.0*sqrt(1.5/M_PI)*sin(theta)*sin(phi));
		if (m==0) return(1.0/2.0*sqrt(3.0/M_PI)*cos(theta));
		if (m==1) return(-1.0/2.0*sqrt(1.5/M_PI)*sin(theta)*cos(phi));
		break;
	case 2:
		if (m==-2) return(1.0/4.0*sqrt(15.0/(2.0*M_PI))*pow(sin(theta),2.0)*sin(2.0*phi));
		if (m==-1) return(1.0/2.0*sqrt(15.0/(2.0*M_PI))*sin(theta)*cos(theta)*sin(phi));
		if (m==0) return(1/4.0*sqrt(5.0/M_PI)*(3.0*pow(cos(theta),2.0)-1.0));
		if (m==1) return(-1.0/2.0*sqrt(15.0/(2.0*M_PI))*sin(theta)*cos(theta)*cos(phi));
		if (m==2) return(1.0/4.0*sqrt(15.0/(2.0*M_PI))*pow(sin(theta),2.0)*cos(2.0*phi));
		break;
	case 3:
		if (m==-3) return(1.0/8.0*sqrt(35.0/M_PI)*pow(sin(theta),3.0)*sin(3.0*phi));
		if (m==-2) return(1.0/4.0*sqrt(52.5/M_PI)*pow(sin(theta),2.0)*cos(theta)*sin(2.0*phi));
		if (m==-1) return(1.0/8.0*sqrt(21.0/M_PI)*sin(theta)*(5.0*pow(cos(theta),2.0)-1.0)*sin(phi));
		if (m==0) return(1.0/4.0*sqrt(7/M_PI)*(5.0*pow(cos(theta),3.0)-3.0*cos(theta)));
		if (m==1) return(-1.0/8.0*sqrt(21.0/M_PI)*sin(theta)*(5.0*pow(cos(theta),2.0)-1.0)*cos(phi));
		if (m==2) return(1.0/4.0*sqrt(52.5/M_PI)*pow(sin(theta),2.0)*cos(theta)*cos(2.0*phi));
		if (m==3) 
return(-1.0/8.0*sqrt(35.0/M_PI)*pow(sin(theta),3.0)*cos(3.0*phi));
		break;
}

return(0.0);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.