diff -ur clearsilver-0.6.2/Makefile clearsilver-0.7.0/Makefile
--- clearsilver-0.6.2/Makefile	Mon May 13 17:43:00 2002
+++ clearsilver-0.7.0/Makefile	Mon May 20 00:39:04 2002
@@ -13,6 +13,10 @@
 
 OUTDIRS = bin libs
 
+# These are blank here... but populated under automated build
+VERSION =
+RELEASE =
+
 all: cs
 
 cs: output_dir
@@ -95,4 +99,4 @@
 
 trakken: cs
 	$(MAKE) -C retrieve
-	$(MAKE) -C trakken
+	$(MAKE) VERSION=$(VERSION) RELEASE=$(RELEASE) -C trakken
diff -ur clearsilver-0.6.2/cgi/cgi.c clearsilver-0.7.0/cgi/cgi.c
--- clearsilver-0.6.2/cgi/cgi.c	Tue Apr 30 20:05:15 2002
+++ clearsilver-0.7.0/cgi/cgi.c	Wed Jul  3 15:33:57 2002
@@ -25,6 +25,7 @@
 #include "util/neo_misc.h"
 #include "util/neo_str.h"
 #include "cgi.h"
+#include "html.h"
 #include "cs/cs.h"
 
 /* HACK for now, until we actually support autoconf/configure */
@@ -140,6 +141,53 @@
   return s;
 }
 
+NEOERR *cgi_js_escape (char *buf, char **esc)
+{
+  int nl = 0;
+  int l = 0;
+  char *s;
+
+  while (buf[l])
+  {
+    if (buf[l] == '/' || buf[l] == '&' || buf[l] == '"' || buf[l] == '\'' ||
+	buf[l] == '\\' || buf[l] == '>' || buf[l] == '<' ||
+	buf[l] < 32 || buf[l] > 122)
+    {
+      nl += 3;
+    } 
+    nl++;
+    l++;
+  }
+
+  s = (char *) malloc (sizeof(char) * (nl + 1));
+  if (s == NULL) 
+    return nerr_raise (NERR_NOMEM, "Unable to allocate memory to escape %s", 
+	buf);
+
+  nl = 0; l = 0;
+  while (buf[l])
+  {
+    if (buf[l] == '/' || buf[l] == '&' || buf[l] == '"' || buf[l] == '\'' ||
+	buf[l] == '\\' || buf[l] == '>' || buf[l] == '<' ||
+	buf[l] < 32 || buf[l] > 122)
+    {
+      s[nl++] = '\\';
+      s[nl++] = 'x';
+      s[nl++] = "0123456789ABCDEF"[buf[l] / 16];
+      s[nl++] = "0123456789ABCDEF"[buf[l] % 16];
+      l++;
+    }
+    else
+    {
+      s[nl++] = buf[l++];
+    }
+  }
+  s[nl] = '\0';
+
+  *esc = s;
+  return STATUS_OK;
+}
+
 NEOERR *cgi_url_escape_more (char *buf, char **esc, char *other)
 {
   int nl = 0;
@@ -764,11 +812,13 @@
   int use_deflate = 0;
   int use_gzip = 0;
   int do_debug = 0;
+  int do_timefooter = 0;
   char *s, *e;
 
   s = hdf_get_value (cgi->hdf, "Query.debug", NULL);
   e = hdf_get_value (cgi->hdf, "Config.DebugPassword", NULL);
   if (s && e && !strcmp(s, e)) do_debug = 1;
+  do_timefooter = hdf_get_int_value (cgi->hdf, "Config.TimeFooter", 1);
 
   dis = ne_timef();
   if (err != STATUS_OK) return nerr_pass (err);
@@ -802,7 +852,7 @@
     s = hdf_get_value (cgi->hdf, "HTTP.UserAgent", NULL);
     if (s)
     {
-      if (strstr(s, "MSIE 4") || strstr(s, "MSIE 5"))
+      if (strstr(s, "MSIE 4") || strstr(s, "MSIE 5") || strstr(s, "MSIE 6"))
       {
 	e = hdf_get_value (cgi->hdf, "HTTP.Accept", NULL);
 	if (e && !strcmp(e, "*/*"))
@@ -847,10 +897,13 @@
     char buf[50];
     int x;
 
-    snprintf (buf, sizeof(buf), "\n<!-- %5.3f:%d -->\n",
-	dis - cgi->time_start, use_deflate || use_gzip);
-    err = string_append (str, buf);
-    if (err != STATUS_OK) return nerr_pass(err);
+    if (do_timefooter)
+    {
+      snprintf (buf, sizeof(buf), "\n<!-- %5.3f:%d -->\n",
+	  dis - cgi->time_start, use_deflate || use_gzip);
+      err = string_append (str, buf);
+      if (err != STATUS_OK) return nerr_pass(err);
+    }
 
     if (do_debug)
     {
@@ -936,6 +989,21 @@
   return nerr_pass(err);
 }
 
+static NEOERR *_html_escape_strfunc(char *str, char **ret)
+{
+  return nerr_pass(html_escape_alloc(str, strlen(str), ret));
+}
+
+static NEOERR *_html_strip_strfunc(char *str, char **ret)
+{
+  return nerr_pass(html_strip_alloc(str, strlen(str), ret));
+}
+
+static NEOERR *_text_html_strfunc(char *str, char **ret)
+{
+  return nerr_pass(convert_text_html_alloc(str, strlen(str), ret));
+}
+
 NEOERR *cgi_display (CGI *cgi, char *cs_file)
 {
   NEOERR *err = STATUS_OK;
@@ -955,6 +1023,16 @@
   {
     err = cs_init (&cs, cgi->hdf);
     if (err != STATUS_OK) break;
+    err = cs_register_strfunc(cs, "url_escape", cgi_url_escape);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(cs, "html_escape", _html_escape_strfunc);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(cs, "text_html", _text_html_strfunc);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(cs, "js_escape", cgi_js_escape);
+    if (err != STATUS_OK) break;
+    err = cs_register_strfunc(cs, "html_strip", _html_strip_strfunc);
+    if (err != STATUS_OK) break;
     err = cs_parse_file (cs, cs_file);
     if (err != STATUS_OK) break;
     if (do_dump)
@@ -1125,12 +1203,12 @@
 }
 
 NEOERR *cgi_cookie_set (CGI *cgi, char *name, char *value, char *path, 
-        char *domain, char *time_str, int persistant)
+        char *domain, char *time_str, int persistent)
 {
   char my_time[256];
 
   if (path == NULL) path = "/";
-  if (persistant)
+  if (persistent)
   {
     if (time_str == NULL)
     {
diff -ur clearsilver-0.6.2/cgi/cgi.h clearsilver-0.7.0/cgi/cgi.h
--- clearsilver-0.6.2/cgi/cgi.h	Mon May 13 13:40:33 2002
+++ clearsilver-0.7.0/cgi/cgi.h	Wed May 15 11:22:29 2002
@@ -305,13 +305,13 @@
  *                 the browser as the sending domain only.
  *        time_str - expiration time string in the following format
  *                   Wdy, DD-Mon-YYYY HH:MM:SS GMT.  Only used if
- *                   persistant.  Default is one year from time of call.
- *        persistant - cookie will be stored by the browser between sessions
+ *                   persistent.  Default is one year from time of call.
+ *        persistent - cookie will be stored by the browser between sessions
  * Output: None
  * Return: NERR_IO
  */
 NEOERR *cgi_cookie_set (CGI *cgi, char *name, char *value, char *path, 
-    char *domain, char *time_str, int persistant);
+    char *domain, char *time_str, int persistent);
 
 /*
  * Function: cgi_cookie_clear - clear browser cookie
diff -ur clearsilver-0.6.2/cgi/date.c clearsilver-0.7.0/cgi/date.c
--- clearsilver-0.6.2/cgi/date.c	Mon Aug  6 14:28:16 2001
+++ clearsilver-0.7.0/cgi/date.c	Tue May 14 12:18:31 2002
@@ -28,6 +28,7 @@
  * prefix.mday
  * prefix.mon   - numeric month
  * prefix.year  - full year (ie, 4 digits)
+ * prefix.2yr  - year (2 digits)
  * prefix.wday  - day of the week
  *
  */
@@ -39,9 +40,13 @@
   int hour, am = 1;
   char buf[256];
 
-  err = hdf_set_value (data, prefix, "");
-  if (err) return nerr_pass(err);
   obj = hdf_get_obj (data, prefix);
+  if (obj == NULL)
+  {
+    err = hdf_set_value (data, prefix, "");
+    if (err) return nerr_pass(err);
+    obj = hdf_get_obj (data, prefix);
+  }
 
   snprintf (buf, sizeof(buf), "%02d", ttm->tm_sec);
   err = hdf_set_value (obj, "sec", buf);
@@ -74,6 +79,9 @@
   err = hdf_set_int_value (obj, "mon", ttm->tm_mon + 1);
   if (err) return nerr_pass(err);
   err = hdf_set_int_value (obj, "year", ttm->tm_year + 1900);
+  if (err) return nerr_pass(err);
+  snprintf(buf, sizeof(buf), "%02d", ttm->tm_year % 100);
+  err = hdf_set_value (obj, "2yr", buf);
   if (err) return nerr_pass(err);
   err = hdf_set_int_value (obj, "wday", ttm->tm_wday);
   if (err) return nerr_pass(err);
diff -ur clearsilver-0.6.2/cgi/html.c clearsilver-0.7.0/cgi/html.c
--- clearsilver-0.6.2/cgi/html.c	Tue Apr 30 20:05:15 2002
+++ clearsilver-0.7.0/cgi/html.c	Tue Jun 25 16:14:47 2002
@@ -536,3 +536,189 @@
   *out = out_s.buf;
   return STATUS_OK;
 }
+
+static char *StripTags[] = {"script", "style", "head"};
+
+/* Replace ampersand with iso-8859-1 character code */
+static char _expand_amp_8859_1_char (char *s)
+{
+  if (s[0] == '\0')
+    return 0;
+
+  switch (s[0]) {
+    case '#':
+      if (s[1] == 'x') return strtol (&s[2], NULL, 16);
+      return strtol (&s[1], NULL, 10);
+    case 'a':
+      if (!strcmp(s, "agrave")) return 0xe0; /* à */
+      if (!strcmp(s, "aacute")) return 0xe1; /* á */
+      if (!strcmp(s, "acirc")) return 0xe2; /* â */
+      if (!strcmp(s, "atilde")) return 0xe3; /* ã */
+      if (!strcmp(s, "auml")) return 0xe4; /* ä */
+      if (!strcmp(s, "aring")) return 0xe5; /* å */
+      if (!strcmp(s, "aelig")) return 0xe6; /* æ */
+      if (!strcmp(s, "amp")) return '&';
+      return 0;
+    case 'c':
+      if (!strcmp(s, "ccedil")) return 0xe7; /* ç */
+      return 0;
+    case 'e':
+      if (!strcmp(s, "egrave")) return 0xe8; /* è */
+      if (!strcmp(s, "eacute")) return 0xe9; /* é */
+      if (!strcmp(s, "ecirc")) return 0xea; /* ê */
+      if (!strcmp(s, "euml")) return 0xeb; /* ë */
+      if (!strcmp(s, "eth")) return 0xf0; /* ð */
+      return 0;
+    case 'i':
+      if (!strcmp(s, "igrave")) return 0xec; /* ì */
+      if (!strcmp(s, "iacute")) return 0xed; /* í */
+      if (!strcmp(s, "icirc")) return 0xee; /* î */
+      if (!strcmp(s, "iuml")) return 0xef; /* ï */
+      return 0;
+    case 'g':
+      if (!strcmp(s, "gt")) return '>';
+      return 0;
+    case 'l':
+      if (!strcmp(s, "lt")) return '<';
+      return 0;
+    case 'n': 
+      if (!strcmp(s, "ntilde")) return 0xf1; /* ñ */
+      if (!strcmp(s, "nbsp")) return ' '; 
+      return 0;
+    case 'o':
+      if (!strcmp(s, "ograve")) return 0xf2; /* ò */
+      if (!strcmp(s, "oacute")) return 0xf3; /* ó */
+      if (!strcmp(s, "ocirc")) return 0xf4; /* ô */
+      if (!strcmp(s, "otilde")) return 0xf5; /* õ */
+      if (!strcmp(s, "ouml")) return 0xf6; /* ö */
+      if (!strcmp(s, "oslash")) return 0xf8; /* ø */
+      return 0;
+    case 'q': /* quot */
+      if (!strcmp(s, "quot")) return '"';
+      return 0;
+    case 's':
+      if (!strcmp(s, "szlig")) return 0xdf; /* ß */
+      return 0;
+    case 't':
+      if (!strcmp(s, "thorn")) return 0xfe; /* þ */
+      return 0;
+    case 'u':
+      if (!strcmp(s, "ugrave")) return 0xf9; /* ù */
+      if (!strcmp(s, "uacute")) return 0xfa; /* ú */
+      if (!strcmp(s, "ucirc")) return 0xfb; /* û */
+      if (!strcmp(s, "uuml")) return 0xfc; /* ü */
+      return 0;
+    case 'y':
+      if (!strcmp(s, "yacute")) return 0xfd; /* ý */
+      	
+  }
+  return 0;
+}
+
+char *html_expand_amp_8859_1(char *amp, char *buf)
+{
+  char ch;
+
+  ch = _expand_amp_8859_1_char(amp);
+  if (ch == '\0')
+  {
+    if (!strcmp(amp, "copy")) return "(C)";
+    return "";
+  }
+  else {
+    buf[0] = ch;
+    buf[1] = '\0';
+    return buf;
+  }
+}
+
+NEOERR *html_strip_alloc(char *src, int slen, char **out)
+{
+  NEOERR *err = STATUS_OK;
+  STRING out_s;
+  int x = 0;
+  int strip_match = -1;
+  int state = 0;
+  char amp[10];
+  char buf[10];
+  int ampl = 0;
+
+  string_init(&out_s);
+  err = string_append (&out_s, "");
+  if (err) return nerr_pass (err);
+
+  while (x < slen)
+  {
+    switch (state) {
+      case 0:
+	/* Default */
+	if (src[x] == '&')
+	{
+	  state = 3;
+	  ampl = 0;
+	}
+	else if (src[x] == '<')
+	{
+	  state = 1;
+	}
+	else
+	{
+	  if (strip_match == -1)
+	  {
+	    err = string_append_char(&out_s, src[x]);
+	    if (err) break;
+	  }
+	}
+	x++;
+	break;
+      case 1:
+	/* Starting TAG */
+	if (src[x] == '>')
+	{
+	  state = 0;
+	}
+	else if (src[x] == '/')
+	{
+	}
+	else 
+	{
+	}
+	x++;
+	break;
+      case 2:
+	/* In TAG */
+	if (src[x] == '>')
+	{
+	  state = 0;
+	}
+	x++;
+	break;
+      case 3:
+	/* In AMP */
+	if (src[x] == ';')
+	{
+	  amp[ampl] = '\0';
+	  state = 0;
+	  err = string_append(&out_s, html_expand_amp_8859_1(amp, buf));
+	  if (err) break;
+	}
+	else
+	{
+	  if (ampl < sizeof(amp)-1)
+	    amp[ampl++] = tolower(src[x]);
+	}
+	x++;
+	break;
+    }
+    if (err) break;
+  }
+
+
+  if (err) 
+  {
+    string_clear (&out_s);
+    return nerr_pass (err);
+  }
+  *out = out_s.buf;
+  return STATUS_OK;
+}
diff -ur clearsilver-0.6.2/cgi/html.h clearsilver-0.7.0/cgi/html.h
--- clearsilver-0.6.2/cgi/html.h	Mon May 13 13:40:33 2002
+++ clearsilver-0.7.0/cgi/html.h	Tue Jun 25 16:14:47 2002
@@ -19,6 +19,7 @@
 
 NEOERR *convert_text_html_alloc (char *src, int slen, char **out);
 NEOERR *html_escape_alloc (char *src, int slen, char **out);
+NEOERR *html_strip_alloc(char *src, int slen, char **out);
 
 __END_DECLS
 
diff -ur clearsilver-0.6.2/cs/Makefile clearsilver-0.7.0/cs/Makefile
--- clearsilver-0.6.2/cs/Makefile	Tue Apr 30 19:57:02 2002
+++ clearsilver-0.7.0/cs/Makefile	Mon Jun 10 18:46:23 2002
@@ -23,7 +23,7 @@
 
 TARGETS = $(CS_LIB) $(CSTEST_EXE) test
 
-CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs test8.cs test9.cs test10.cs test11.cs test12.cs test13.cs
+CS_TESTS = test.cs test2.cs test3.cs test4.cs test5.cs test6.cs test7.cs test8.cs test9.cs test10.cs test11.cs test12.cs test13.cs test14.cs test15.cs
 
 all: $(TARGETS)
 
diff -ur clearsilver-0.6.2/cs/cs.h clearsilver-0.7.0/cs/cs.h
--- clearsilver-0.6.2/cs/cs.h	Mon May 13 13:40:35 2002
+++ clearsilver-0.7.0/cs/cs.h	Mon Jun 10 18:46:23 2002
@@ -50,51 +50,59 @@
 typedef enum
 {
   /* Unary operators */
-  CS_OP_EXISTS = (1<<0),
-  CS_OP_NOT = (1<<1),
+  CS_OP_NONE = (1<<0),
+  CS_OP_EXISTS = (1<<1),
+  CS_OP_NOT = (1<<2),
+  CS_OP_NUM = (1<<3),
 
   /* Binary Operators */
-  CS_OP_EQUAL = (1<<2),
-  CS_OP_NEQUAL = (1<<3),
-  CS_OP_LT = (1<<4),
-  CS_OP_LTE = (1<<5),
-  CS_OP_GT = (1<<6),
-  CS_OP_GTE = (1<<7),
-  CS_OP_AND = (1<<8),
-  CS_OP_OR = (1<<9),
-  CS_OP_ADD = (1<<10),
-  CS_OP_SUB = (1<<11),
-  CS_OP_MULT = (1<<12),
-  CS_OP_DIV = (1<<13),
-  CS_OP_MOD = (1<<14),
+  CS_OP_EQUAL = (1<<4),
+  CS_OP_NEQUAL = (1<<5),
+  CS_OP_LT = (1<<6),
+  CS_OP_LTE = (1<<7),
+  CS_OP_GT = (1<<8),
+  CS_OP_GTE = (1<<9),
+  CS_OP_AND = (1<<10),
+  CS_OP_OR = (1<<11),
+  CS_OP_ADD = (1<<12),
+  CS_OP_SUB = (1<<13),
+  CS_OP_MULT = (1<<14),
+  CS_OP_DIV = (1<<15),
+  CS_OP_MOD = (1<<16),
 
   /* Associative Operators */
-  CS_OP_LPAREN = (1<<15),
-  CS_OP_RPAREN = (1<<16),
-  CS_OP_LBRACKET = (1<<17),
-  CS_OP_RBRACKET = (1<<18),
-
+  CS_OP_LPAREN = (1<<17),
+  CS_OP_RPAREN = (1<<18),
+  CS_OP_LBRACKET = (1<<19),
+  CS_OP_RBRACKET = (1<<20),
 
   /* Types */
-  CS_TYPE_STRING = (1<<19),
-  CS_TYPE_NUM = (1<<20),
-  CS_TYPE_VAR = (1<<21),
-  CS_TYPE_VAR_NUM = (1<<22),
-  CS_TYPE_MACRO = (1<<23),
-  CS_TYPE_EXPR = (1<<24)
+  CS_TYPE_STRING = (1<<21),
+  CS_TYPE_NUM = (1<<22),
+  CS_TYPE_VAR = (1<<23),
+  CS_TYPE_VAR_NUM = (1<<24),
+
+  /* Not real types... */
+  CS_TYPE_MACRO = (1<<25),
+  CS_TYPE_FUNCTION = (1<<26)
 } CSTOKEN_TYPE;
 
+#define CS_OPS_UNARY (CS_OP_EXISTS | CS_OP_NOT | CS_OP_NUM)
 #define CS_TYPES (CS_TYPE_STRING | CS_TYPE_NUM | CS_TYPE_VAR | CS_TYPE_VAR_NUM)
 #define CS_TYPES_VAR (CS_TYPE_VAR | CS_TYPE_VAR_NUM)
 #define CS_TYPES_CONST (CS_TYPE_STRING | CS_TYPE_NUM)
 #define CS_ASSOC (CS_OP_RPAREN | CS_OP_RBRACKET)
 
+typedef struct _parse CSPARSE;
+typedef struct _funct CS_FUNCTION;
+
 typedef struct _arg
 {
   CSTOKEN_TYPE op_type;
   char *s;
   long int n;
   int alloc;
+  struct _funct *function;
   struct _macro *macro;
   struct _arg *expr1;
   struct _arg *expr2;
@@ -144,7 +152,22 @@
   struct _macro *next;
 } CS_MACRO;
 
-typedef struct _parse
+typedef NEOERR* (*CSFUNCTION)(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result);
+typedef NEOERR* (*CSSTRFUNC)(char *str, char **ret);
+
+struct _funct
+{
+  char *name;
+  int name_len;
+  int n_args;
+
+  CSFUNCTION function;
+  CSSTRFUNC str_func;
+
+  struct _funct *next;
+};
+
+struct _parse
 {
   char *context;         /* A string identifying where the parser is parsing */
   int in_file;           /* Indicates if current context is a file */
@@ -160,12 +183,13 @@
 
   CS_LOCAL_MAP *locals;
   CS_MACRO *macros;
+  CS_FUNCTION *functions;
 
   /* Output */
   void *output_ctx;
   CSOUTFUNC output_cb;
 
-} CSPARSE;
+};
 
 /*
  * Function: cs_init - create and initialize a CS context
@@ -279,6 +303,33 @@
  * Return: None
  */
 void cs_destroy (CSPARSE **parse);
+
+/*
+ * Function: cs_register_strfunc - register a string handling function
+ * Description: cs_register_strfunc will register a string function that
+ *              can be called during CS render.  This not-callback is 
+ *              designed to allow for string formating/escaping
+ *              functions that are not built-in to CS (since CS is not
+ *              HTML specific, for instance, but it is very useful to
+ *              have CS have functions for javascript/html/url
+ *              escaping).  Note that we explicitly don't provide any
+ *              associated data or anything to attempt to keep you from
+ *              using this as a generic callback...
+ *              The format of a CSSTRFUNC is:
+ *                 NEOERR * str_func(char *in, char **out);
+ *              This function should not modify the input string, and 
+ *              should allocate the output string with a libc function.
+ *              (as we will call free on it)
+ * Input: parse - a pointer to a CSPARSE structure initialized with cs_init()
+ *        funcname - the name for the CS function call
+ *                   Note that registering a duplicate funcname will
+ *                   raise a NERR_DUPLICATE error
+ *        str_func - a CSSTRFUNC not-callback
+ * Return: NERR_NOMEM - failure to allocate any memory for data structures
+ *         NERR_DUPLICATE - funcname already registered
+ *          
+ */
+NEOERR *cs_register_strfunc(CSPARSE *parse, char *funcname, CSSTRFUNC str_func);
 
 __END_DECLS
 
diff -ur clearsilver-0.6.2/cs/csparse.c clearsilver-0.7.0/cs/csparse.c
--- clearsilver-0.6.2/cs/csparse.c	Mon May 13 17:42:18 2002
+++ clearsilver-0.7.0/cs/csparse.c	Mon Jun 10 18:52:59 2002
@@ -146,6 +146,9 @@
   {NULL},
 };
 
+
+/* **** CS alloc/dealloc ******************************************** */
+
 static int NodeNumber = 0;
 
 static NEOERR *alloc_node (CSTREE **node)
@@ -211,78 +214,16 @@
   *macro = NULL;
 }
 
-NEOERR *cs_init (CSPARSE **parse, HDF *hdf)
-{
-  NEOERR *err = STATUS_OK;
-  CSPARSE *my_parse;
-  STACK_ENTRY *entry;
-
-  err = nerr_init();
-  if (err != STATUS_OK) return nerr_pass (err);
-
-  my_parse = (CSPARSE *) calloc (1, sizeof (CSPARSE));
-  if (my_parse == NULL)
-    return nerr_raise (NERR_NOMEM, "Unable to allocate memory for CSPARSE");
-
-
-  err = uListInit (&(my_parse->stack), 10, 0);
-  if (err != STATUS_OK)
-  {
-    free(my_parse);
-    return nerr_pass(err);
-  }
-  err = uListInit (&(my_parse->alloc), 10, 0);
-  if (err != STATUS_OK)
-  {
-    free(my_parse);
-    return nerr_pass(err);
-  }
-  err = alloc_node (&(my_parse->tree));
-  if (err != STATUS_OK)
-  {
-    cs_destroy (&my_parse);
-    return nerr_pass(err);
-  }
-  my_parse->current = my_parse->tree;
-  my_parse->next = &(my_parse->current->next);
-
-  entry = (STACK_ENTRY *) calloc (1, sizeof (STACK_ENTRY));
-  if (entry == NULL)
-  {
-    cs_destroy (&my_parse);
-    return nerr_raise (NERR_NOMEM, 
-	"Unable to allocate memory for stack entry");
-  }
-  entry->state = ST_GLOBAL;
-  entry->tree = my_parse->current;
-  entry->location = 0;
-  err = uListAppend(my_parse->stack, entry);
-  if (err != STATUS_OK) {
-    free (entry);
-    cs_destroy(&my_parse);
-    return nerr_pass(err);
-  }
-  my_parse->hdf = hdf;
-
-  *parse = my_parse;
-  return STATUS_OK;
-}
-
-void cs_destroy (CSPARSE **parse)
+static void dealloc_function (CS_FUNCTION **csf)
 {
-  CSPARSE *my_parse = *parse;
+  CS_FUNCTION *my_csf;
 
-  if (my_parse == NULL)
-    return;
-
-  uListDestroy (&(my_parse->stack), ULIST_FREE);
-  uListDestroy (&(my_parse->alloc), ULIST_FREE);
-
-  dealloc_macro(&my_parse->macros);
-  dealloc_node(&(my_parse->tree));
-
-  free(my_parse);
-  *parse = NULL;
+  if (*csf == NULL) return;
+  my_csf = *csf;
+  if (my_csf->name) free (my_csf->name);
+  if (my_csf->next) dealloc_function (&(my_csf->next));
+  free (my_csf);
+  *csf = NULL;
 }
 
 static int find_open_delim (char *buf, int x, int len)
@@ -726,9 +667,14 @@
   { TRUE, "!=", CS_OP_NEQUAL },
   { TRUE, "||", CS_OP_OR },
   { TRUE, "&&", CS_OP_AND },
+  { FALSE, "!", CS_OP_NOT },
+/* For now, we are still treating this special instead of as an op
+ * If we make this an op, then we'd have to determine how to handle
+ * NUM types without doing something like #"5" */
+/*  { FALSE, "#", CS_OP_NUM }, */
+  { FALSE, "?", CS_OP_EXISTS },
   { FALSE, "<", CS_OP_LT },
   { FALSE, ">", CS_OP_GT },
-  { FALSE, "!", CS_OP_NOT },
   { FALSE, "+", CS_OP_ADD },
   { FALSE, "-", CS_OP_SUB },
   { FALSE, "*", CS_OP_MULT },
@@ -848,12 +794,13 @@
    0
 };
 
-static char *expand_token_type(CSTOKEN_TYPE t_type)
+static char *expand_token_type(CSTOKEN_TYPE t_type, int more)
 {
   switch (t_type)
   {
     case CS_OP_EXISTS: return "?";
     case CS_OP_NOT: return "!";
+    case CS_OP_NUM: return "#";
     case CS_OP_EQUAL: return "==";
     case CS_OP_NEQUAL: return "!=";
     case CS_OP_LT: return "<";
@@ -871,10 +818,10 @@
     case CS_OP_RPAREN: return ")";
     case CS_OP_LBRACKET: return "[";
     case CS_OP_RBRACKET: return "]";
-    case CS_TYPE_STRING: return "s";
-    case CS_TYPE_NUM: return "n";
-    case CS_TYPE_VAR: return "v";
-    case CS_TYPE_VAR_NUM: return "vn";
+    case CS_TYPE_STRING: return more ? "STRING" : "s";
+    case CS_TYPE_NUM: return more ? "NUM" : "n";
+    case CS_TYPE_VAR: return more ? "VAR" : "v";
+    case CS_TYPE_VAR_NUM: return more ? "VARNUM" : "vn";
     default: return "u";
   }
   return "u";
@@ -891,35 +838,45 @@
 #if DEBUG_EXPR_PARSE
   for (x = 0; x < ntokens; x++)
   {
-    fprintf (stderr, "%s ", expand_token_type(tokens[x].type));
+    fprintf (stderr, "%s ", expand_token_type(tokens[x].type, 0));
   }
   fprintf(stderr, "\n");
 #endif
 
-  if (ntokens == 1 || (ntokens == 2 && tokens[0].type == CS_OP_NOT))
+  if (ntokens == 1)
   {
     x = 0;
-    if (tokens[0].type == CS_OP_NOT) x = 1;
-    if (tokens[x].type & CS_TYPES)
+    if (tokens[0].type & CS_TYPES)
     {
-      arg->s = tokens[x].value;
-      if (tokens[x].len >= 0)
-	arg->s[tokens[x].len] = '\0';
-      arg->op_type = tokens[x].type;
+      arg->s = tokens[0].value;
+      if (tokens[0].len >= 0)
+	arg->s[tokens[0].len] = '\0';
+      arg->op_type = tokens[0].type;
 
       if (tokens[x].type == CS_TYPE_NUM)
 	arg->n = strtol(arg->s, NULL, 0);
-      if (tokens[0].type == CS_OP_NOT) arg->op_type |= CS_OP_NOT;
       return STATUS_OK;
     }
     else
     {
       return nerr_raise (NERR_PARSE, 
 	  "%s Terminal token is not an argument, type is %s",
-	  find_context(parse, -1, tmp, sizeof(tmp)), expand_token_type(tokens[0].type));
+	  find_context(parse, -1, tmp, sizeof(tmp)), expand_token_type(tokens[0].type, 0));
     }
   }
 
+  if (ntokens == 2 && (tokens[0].type & CS_OPS_UNARY))
+  {
+    arg->op_type = tokens[0].type;
+    arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
+    if (arg->expr1 == NULL)
+      return nerr_raise (NERR_NOMEM, 
+	  "%s Unable to allocate memory for expression", 
+	  find_context(parse, -1, tmp, sizeof(tmp)));
+    err = parse_expr2(parse, tokens + 1, 1, arg->expr1);
+    return nerr_pass(err);
+  }
+
   while (BinaryOpOrder[op])
   {
     x = ntokens-1;
@@ -1006,6 +963,65 @@
     return nerr_pass(parse_expr2(parse, tokens + 1, ntokens-2, arg));
   }
 
+  /* Unary op against an entire expression */
+  if ((tokens[0].type & CS_OPS_UNARY) && tokens[1].type == CS_OP_LPAREN && 
+      tokens[x].type == CS_OP_RPAREN)
+  {
+    arg->op_type = tokens[0].type;
+    arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
+    if (arg->expr1 == NULL)
+      return nerr_raise (NERR_NOMEM, 
+	  "%s Unable to allocate memory for expression", 
+	  find_context(parse, -1, tmp, sizeof(tmp)));
+    err = parse_expr2(parse, tokens + 2, ntokens-3, arg->expr1);
+    return nerr_pass(err);
+  }
+  if (tokens[0].type & CS_OPS_UNARY)
+  {
+    arg->op_type = tokens[0].type;
+    arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
+    if (arg->expr1 == NULL)
+      return nerr_raise (NERR_NOMEM, 
+	  "%s Unable to allocate memory for expression", 
+	  find_context(parse, -1, tmp, sizeof(tmp)));
+    err = parse_expr2(parse, tokens + 1, ntokens-1, arg->expr1);
+    return nerr_pass(err);
+  }
+
+  /* function call (we only handle a single arg for now) */
+  if ((tokens[0].type & CS_TYPE_VAR) && tokens[1].type == CS_OP_LPAREN && 
+      tokens[x].type == CS_OP_RPAREN)
+  {
+    CS_FUNCTION *csf;
+
+    if (tokens[0].len >= 0)
+      tokens[0].value[tokens[0].len] = '\0';
+
+    arg->op_type = CS_TYPE_FUNCTION;
+    csf = parse->functions;
+    while (csf != NULL)
+    {
+      if (!strcmp(tokens[0].value, csf->name))
+      {
+	arg->function = csf;
+	break;
+      }
+      csf = csf->next;
+    }
+    if (csf == NULL)
+    {
+      return nerr_raise (NERR_PARSE, "%s Unknown function %s called",
+	  find_context(parse, -1, tmp, sizeof(tmp)), tokens[0].value);
+    }
+    arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
+    if (arg->expr1 == NULL)
+      return nerr_raise (NERR_NOMEM, 
+	  "%s Unable to allocate memory for expression", 
+	  find_context(parse, -1, tmp, sizeof(tmp)));
+    err = parse_expr2(parse, tokens + 2, ntokens-3, arg->expr1);
+    return nerr_pass(err);
+  }
+
   return nerr_raise (NERR_PARSE, "%s Bad Expression",
       find_context(parse, -1, tmp, sizeof(tmp)));
 }
@@ -1297,25 +1313,17 @@
 
 char *arg_eval (CSPARSE *parse, CSARG *arg)
 {
-  if (arg->op_type & CS_OP_NOT)
-  {
-    ne_warn ("String eval shouldn't have a NOT op");
-    return NULL;
-  }
-  else
+  switch ((arg->op_type & CS_TYPES))
   {
-    switch ((arg->op_type & CS_TYPES))
-    {
-      case CS_TYPE_STRING:
-	return arg->s;
-      case CS_TYPE_VAR:
-	return var_lookup (parse, arg->s);
-      case CS_TYPE_NUM:
-      case CS_TYPE_VAR_NUM:
-      default:
-	ne_warn ("Unsupported type %d in arg_eval", arg->op_type);
-	return NULL;
-    }
+    case CS_TYPE_STRING:
+      return arg->s;
+    case CS_TYPE_VAR:
+      return var_lookup (parse, arg->s);
+    case CS_TYPE_NUM:
+    case CS_TYPE_VAR_NUM:
+    default:
+      ne_warn ("Unsupported type %s in arg_eval", expand_token_type(arg->op_type, 1));
+      return NULL;
   }
 }
 
@@ -1337,21 +1345,23 @@
       v = var_int_lookup (parse, arg->s);
       break;
     default:
-      ne_warn ("Unsupported type %s in arg_eval_num", arg->op_type);
+      ne_warn ("Unsupported type %s in arg_eval_num", expand_token_type(arg->op_type, 1));
       v = 0;
       break;
   }
-  if (arg->op_type & CS_OP_NOT)
-    v = !v;
   return v;
 }
 
 #if DEBUG_EXPR_EVAL
 static char *expand_arg (CSARG *arg)
 {
-  fprintf(stderr, "op: %s alloc: %d value: ", expand_token_type(arg->op_type), arg->alloc);
+  fprintf(stderr, "op: %s alloc: %d value: ", expand_token_type(arg->op_type, 0), arg->alloc);
   if (arg->op_type & CS_OP_NOT)
     fprintf(stderr, "!");
+  if (arg->op_type & CS_OP_NUM)
+    fprintf(stderr, "#");
+  if (arg->op_type & CS_OP_EXISTS)
+    fprintf(stderr, "?");
   if (arg->op_type & (CS_TYPE_VAR_NUM | CS_TYPE_NUM))
     fprintf(stderr, "#");
   if (arg->op_type & CS_TYPE_NUM)
@@ -1396,191 +1406,248 @@
     fprintf(stderr, "arg1 ");
     expand_arg(&arg1);
 #endif
-    err = eval_expr (parse, expr->expr2, &arg2);
-#if DEBUG_EXPR_EVAL
-    fprintf(stderr, "arg2 ");
-    expand_arg(&arg2);
-#endif
-    if (err) return nerr_pass(err);
-
-    if (expr->op_type == CS_OP_LBRACKET)
+    if (expr->op_type & CS_TYPE_FUNCTION)
     {
-      /* the bracket op is essentially hdf array lookups, which just
-       * means appending .0 */
-      result->op_type = CS_TYPE_VAR;
-      result->alloc = 1;
-      if (arg2.op_type & (CS_TYPE_VAR_NUM | CS_TYPE_NUM))
-      {
-	n2 = arg_eval_num (parse, &arg2);
-	result->s = sprintf_alloc("%s.%d", arg1.s, n2);
-	if (result->s == NULL)
-	  return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %d", arg1.s, n2);
-      }
-      else
-      {
-	s2 = arg_eval (parse, &arg2);
-	if (s2 && s2[0])
-	{
-	  result->s = sprintf_alloc("%s.%s", arg1.s, s2);
-	  if (result->s == NULL)
-	    return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %s", arg1.s, s2);
-	}
-	else
-	{
-	  /* if s2 doesn't match anything, then the whole thing is empty */
-	  result->s = "";
-	  result->alloc = 0;
-	}
-      }
+      if (expr->function == NULL || expr->function->function == NULL)
+	return nerr_raise(NERR_ASSERT, 
+	    "Function is NULL in attempt to evaluate function call %s", 
+	    (expr->function) ? expr->function->name : "");
+
+      err = expr->function->function(parse, expr->function, &arg1, result);
+      if (err) return nerr_pass(err);
     }
-    else if ((arg1.op_type & (CS_OP_NOT | CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
-	(arg2.op_type & (CS_OP_NOT | CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
-	(expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD)))
+    else if (expr->op_type & CS_OPS_UNARY)
     {
-      /* eval as num */
-
       result->op_type = CS_TYPE_NUM;
-      n1 = arg_eval_num (parse, &arg1);
-      n2 = arg_eval_num (parse, &arg2);
-
-      switch (expr->op_type)
-      {
-	case CS_OP_EQUAL:
-	  result->n = (n1 == n2) ? 1 : 0;
-	  break;
-	case CS_OP_NEQUAL:
-	  result->n = (n1 != n2) ? 1 : 0;
-	  break;
-	case CS_OP_LT:
-	  result->n = (n1 < n2) ? 1 : 0;
-	  break;
-	case CS_OP_LTE:
-	  result->n = (n1 <= n2) ? 1 : 0;
-	  break;
-	case CS_OP_GT:
-	  result->n = (n1 > n2) ? 1 : 0;
-	  break;
-	case CS_OP_GTE:
-	  result->n = (n1 >= n2) ? 1 : 0;
-	  break;
-	case CS_OP_AND:
-	  result->n = (n1 && n2) ? 1 : 0;
-	  break;
-	case CS_OP_OR:
-	  result->n = (n1 || n2) ? 1 : 0;
-	  break;
-	case CS_OP_ADD:
-	  result->n = (n1 + n2);
-	  break;
-	case CS_OP_SUB:
-	  result->n = (n1 - n2);
-	  break;
-	case CS_OP_MULT:
-	  result->n = (n1 * n2);
+      switch (expr->op_type) {
+	case CS_OP_NOT:
+	  if (arg1.op_type & CS_TYPE_VAR)
+	  {
+	    /* This case is a "not exist" test */
+	    s1 = arg_eval (parse, &arg1);
+	    if (s1 == NULL || *s1 == '\0')
+	      result->n = 1;
+	    else
+	      result->n = 0;
+	  }
+	  else 
+	  {
+	    result->n = arg_eval_num (parse, &arg1);
+	    result->n = result->n ? 0 : 1;
+	  }
 	  break;
-	case CS_OP_DIV:
-	  if (n2 == 0) result->n = UINT_MAX;
-	  else result->n = (n1 / n2);
+	case CS_OP_EXISTS:
+	  if (arg1.op_type & (CS_TYPE_VAR | CS_TYPE_VAR_NUM))
+	  {
+	    s1 = arg_eval (parse, &arg1);
+	    if (s1 == NULL || *s1 == '\0')
+	      result->n = 0;
+	    else
+	      result->n = 1;
+	  }
+	  else
+	  {
+	    /* All numbers/strings exist */
+	    result->n = 1;
+	  }
 	  break;
-	case CS_OP_MOD:
-	  if (n2 == 0) result->n = 0;
-	  else result->n = (n1 % n2);
+	case CS_OP_NUM:
+	  result->n = arg_eval_num (parse, &arg1);
 	  break;
 	default:
-	  ne_warn ("Unsupported op %d in eval_expr", expr->op_type);
+	  result->n = 0;
+	  ne_warn ("Unsupported op %s in eval_expr", expand_token_type(expr->op_type, 1));
 	  break;
       }
     }
-    else /* eval as string */
+    else
     {
-      result->op_type = CS_TYPE_NUM;
-      s1 = arg_eval (parse, &arg1);
-      s2 = arg_eval (parse, &arg2);
+      err = eval_expr (parse, expr->expr2, &arg2);
+#if DEBUG_EXPR_EVAL
+      fprintf(stderr, "arg2 ");
+      expand_arg(&arg2);
+#endif
+      if (err) return nerr_pass(err);
 
-      if ((s1 == NULL) || (s2 == NULL))
+      if (expr->op_type == CS_OP_LBRACKET)
+      {
+	/* the bracket op is essentially hdf array lookups, which just
+	 * means appending .0 */
+	result->op_type = CS_TYPE_VAR;
+	result->alloc = 1;
+	if (arg2.op_type & (CS_TYPE_VAR_NUM | CS_TYPE_NUM))
+	{
+	  n2 = arg_eval_num (parse, &arg2);
+	  result->s = sprintf_alloc("%s.%d", arg1.s, n2);
+	  if (result->s == NULL)
+	    return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %d", arg1.s, n2);
+	}
+	else
+	{
+	  s2 = arg_eval (parse, &arg2);
+	  if (s2 && s2[0])
+	  {
+	    result->s = sprintf_alloc("%s.%s", arg1.s, s2);
+	    if (result->s == NULL)
+	      return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %s", arg1.s, s2);
+	  }
+	  else
+	  {
+	    /* if s2 doesn't match anything, then the whole thing is empty */
+	    result->s = "";
+	    result->alloc = 0;
+	  }
+	}
+      }
+      else if ((arg1.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
+	  (arg2.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM)) ||
+	  (expr->op_type & (CS_OP_AND | CS_OP_OR | CS_OP_SUB | CS_OP_MULT | CS_OP_DIV | CS_OP_MOD)))
       {
+	/* eval as num */
+
+	result->op_type = CS_TYPE_NUM;
+	n1 = arg_eval_num (parse, &arg1);
+	n2 = arg_eval_num (parse, &arg2);
+
 	switch (expr->op_type)
 	{
 	  case CS_OP_EQUAL:
-	    result->n = (s1 == s2) ? 1 : 0;
+	    result->n = (n1 == n2) ? 1 : 0;
 	    break;
 	  case CS_OP_NEQUAL:
-	    result->n = (s1 != s2) ? 1 : 0;
+	    result->n = (n1 != n2) ? 1 : 0;
 	    break;
 	  case CS_OP_LT:
-	    result->n = ((s1 == NULL) && (s2 != NULL)) ? 1 : 0;
+	    result->n = (n1 < n2) ? 1 : 0;
 	    break;
 	  case CS_OP_LTE:
-	    result->n = (s1 == NULL) ? 1 : 0;
+	    result->n = (n1 <= n2) ? 1 : 0;
 	    break;
 	  case CS_OP_GT:
-	    result->n = ((s1 != NULL) && (s2 == NULL)) ? 1 : 0;
+	    result->n = (n1 > n2) ? 1 : 0;
 	    break;
 	  case CS_OP_GTE:
-	    result->n = (s2 == NULL) ? 1 : 0;
-	    break;
-	  case CS_OP_ADD:
-	    /* be sure to transfer ownership of the string here */
-	    result->op_type = CS_TYPE_STRING;
-	    if (s1 == NULL) 
-	    {
-	      result->s = s2;
-	      result->alloc = arg2.alloc;
-	      arg2.alloc = 0;
-	    }
-	    else
-	    {
-	      result->s = s1;
-	      result->alloc = arg1.alloc;
-	      arg1.alloc = 0;
-	    }
-	    break;
-	  default:
-	    ne_warn ("Unsupported op %d in eval_expr", expr->op_type);
+	    result->n = (n1 >= n2) ? 1 : 0;
 	    break;
-	}
-      }
-      else
-      {
-	out = strcmp (s1, s2);
-	switch (expr->op_type)
-	{
-	  case CS_OP_EQUAL:
-	    result->n = (!out) ? 1 : 0;
+	  case CS_OP_AND:
+	    result->n = (n1 && n2) ? 1 : 0;
 	    break;
-	  case CS_OP_NEQUAL:
-	    result->n = (out) ? 1 : 0;
+	  case CS_OP_OR:
+	    result->n = (n1 || n2) ? 1 : 0;
 	    break;
-	  case CS_OP_LT:
-	    result->n = (out < 0) ? 1 : 0;
+	  case CS_OP_ADD:
+	    result->n = (n1 + n2);
 	    break;
-	  case CS_OP_LTE:
-	    result->n = (out <= 0) ? 1 : 0;
+	  case CS_OP_SUB:
+	    result->n = (n1 - n2);
 	    break;
-	  case CS_OP_GT:
-	    result->n = (out > 0) ? 1 : 0;
+	  case CS_OP_MULT:
+	    result->n = (n1 * n2);
 	    break;
-	  case CS_OP_GTE:
-	    result->n = (out >= 0) ? 1 : 0;
+	  case CS_OP_DIV:
+	    if (n2 == 0) result->n = UINT_MAX;
+	    else result->n = (n1 / n2);
 	    break;
-	  case CS_OP_ADD:
-	    result->op_type = CS_TYPE_STRING;
-	    result->alloc = 1;
-	    result->s = (char *) calloc ((strlen(s1) + strlen(s2) + 1), sizeof(char));
-	    if (result->s == NULL)
-	      return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate strings in expression: %s + %s", s1, s2);
-	    strcpy(result->s, s1);
-	    strcat(result->s, s2);
+	  case CS_OP_MOD:
+	    if (n2 == 0) result->n = 0;
+	    else result->n = (n1 % n2);
 	    break;
 	  default:
-	    ne_warn ("Unsupported op %d in eval_expr", expr->op_type);
+	    ne_warn ("Unsupported op %s in eval_expr", expand_token_type(expr->op_type, 1));
 	    break;
 	}
       }
+      else /* eval as string */
+      {
+	result->op_type = CS_TYPE_NUM;
+	s1 = arg_eval (parse, &arg1);
+	s2 = arg_eval (parse, &arg2);
+
+	if ((s1 == NULL) || (s2 == NULL))
+	{
+	  switch (expr->op_type)
+	  {
+	    case CS_OP_EQUAL:
+	      result->n = (s1 == s2) ? 1 : 0;
+	      break;
+	    case CS_OP_NEQUAL:
+	      result->n = (s1 != s2) ? 1 : 0;
+	      break;
+	    case CS_OP_LT:
+	      result->n = ((s1 == NULL) && (s2 != NULL)) ? 1 : 0;
+	      break;
+	    case CS_OP_LTE:
+	      result->n = (s1 == NULL) ? 1 : 0;
+	      break;
+	    case CS_OP_GT:
+	      result->n = ((s1 != NULL) && (s2 == NULL)) ? 1 : 0;
+	      break;
+	    case CS_OP_GTE:
+	      result->n = (s2 == NULL) ? 1 : 0;
+	      break;
+	    case CS_OP_ADD:
+	      /* be sure to transfer ownership of the string here */
+	      result->op_type = CS_TYPE_STRING;
+	      if (s1 == NULL) 
+	      {
+		result->s = s2;
+		result->alloc = arg2.alloc;
+		arg2.alloc = 0;
+	      }
+	      else
+	      {
+		result->s = s1;
+		result->alloc = arg1.alloc;
+		arg1.alloc = 0;
+	      }
+	      break;
+	    default:
+	      ne_warn ("Unsupported op %s in eval_expr", expand_token_type(expr->op_type, 1));
+	      break;
+	  }
+	}
+	else
+	{
+	  out = strcmp (s1, s2);
+	  switch (expr->op_type)
+	  {
+	    case CS_OP_EQUAL:
+	      result->n = (!out) ? 1 : 0;
+	      break;
+	    case CS_OP_NEQUAL:
+	      result->n = (out) ? 1 : 0;
+	      break;
+	    case CS_OP_LT:
+	      result->n = (out < 0) ? 1 : 0;
+	      break;
+	    case CS_OP_LTE:
+	      result->n = (out <= 0) ? 1 : 0;
+	      break;
+	    case CS_OP_GT:
+	      result->n = (out > 0) ? 1 : 0;
+	      break;
+	    case CS_OP_GTE:
+	      result->n = (out >= 0) ? 1 : 0;
+	      break;
+	    case CS_OP_ADD:
+	      result->op_type = CS_TYPE_STRING;
+	      result->alloc = 1;
+	      result->s = (char *) calloc ((strlen(s1) + strlen(s2) + 1), sizeof(char));
+	      if (result->s == NULL)
+		return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate strings in expression: %s + %s", s1, s2);
+	      strcpy(result->s, s1);
+	      strcat(result->s, s2);
+	      break;
+	    default:
+	      ne_warn ("Unsupported op %s in eval_expr", expand_token_type(expr->op_type, 1));
+	      break;
+	  }
+	}
+      }
+
+      if (arg1.alloc) free(arg1.s);
+      if (arg2.alloc) free(arg2.s);
     }
-    
-    if (arg1.alloc) free(arg1.s);
-    if (arg2.alloc) free(arg2.s);
   }
 #if DEBUG_EXPR_EVAL
   fprintf(stderr, "result ");
@@ -1646,11 +1713,13 @@
       do {
 	err = cs_init(&cs, parse->hdf);
 	if (err) break;
+	cs->functions = parse->functions;
 	err = cs_parse_string(cs, s, strlen(s));
 	if (err) break;
 	err = cs_render(cs, parse->output_ctx, parse->output_cb);
 	if (err) break;
       } while (0);
+      cs->functions = NULL;
       cs_destroy(&cs);
     }
   }
@@ -1686,6 +1755,7 @@
       do {
 	err = cs_init(&cs, parse->hdf);
 	if (err) break;
+	cs->functions = parse->functions;
 	err = cs_parse_file(cs, s);
 	if (!(node->flags & CSF_REQUIRED))
 	{
@@ -1695,6 +1765,7 @@
 	err = cs_render(cs, parse->output_ctx, parse->output_cb);
 	if (err) break;
       } while (0);
+      cs->functions = NULL;
       cs_destroy(&cs);
     }
   }
@@ -2220,6 +2291,16 @@
   node->arg1.op_type = CS_TYPE_MACRO;
   node->arg1.macro = macro;
 
+  a = strrchr(s, ')');
+  if (a == NULL)
+  {
+    dealloc_node(&node);
+    return nerr_raise (NERR_PARSE, 
+	"%s Missing right paren in def %s",
+	find_context(parse, -1, tmp, sizeof(tmp)), arg);
+  }
+  *a = '\0';
+
   x = 0;
   while (*s)
   {
@@ -2242,16 +2323,15 @@
       larg = carg;
     }
     x++;
-    a = strpbrk(s, ",)");
+    a = strpbrk(s, ",");
     if (a == NULL)
     {
-      err = nerr_raise (NERR_PARSE, 
-	  "%s Missing right paren in def %s",
-	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
-      break;
+      last = TRUE;
+    }
+    else
+    {
+      *a = '\0';
     }
-    if (*a == ')') last = TRUE;
-    *a = '\0';
     err = parse_expr (parse, s, carg);
     if (err) break;
     if (last == TRUE) break;
@@ -2346,7 +2426,7 @@
     }
     else
     {
-      ne_warn("Unsupported type %d in call_expr", val.op_type);
+      ne_warn("Unsupported type %s in call_expr", expand_token_type(val.op_type, 1));
     }
     if (val.alloc) free(val.s);
     map->next = parse->locals;
@@ -2636,7 +2716,6 @@
   *next = node->next;
   return STATUS_OK;
 }
-
 static NEOERR *render_node (CSPARSE *parse, CSTREE *node)
 {
   NEOERR *err = STATUS_OK;
@@ -2663,6 +2742,199 @@
   node = parse->tree;
   return nerr_pass (render_node(parse, node));
 }
+
+/* **** Functions ******************************************** */
+
+static NEOERR *_register_function(CSPARSE *parse, char *funcname, int n_args, CSFUNCTION function)
+{
+  CS_FUNCTION *csf;
+
+  if (n_args != 1)
+    return nerr_raise(NERR_ASSERT, "Currently, only 1 argument functions are supported");
+
+  /* Should we validate the parseability of the name? */
+
+  csf = parse->functions;
+  while (csf != NULL)
+  {
+    if (!strcmp(csf->name, funcname) && csf->function != function)
+    {
+      return nerr_raise(NERR_DUPLICATE, 
+	  "Attempt to register duplicate function %s", funcname);
+    }
+    csf = csf->next;
+  }
+  csf = (CS_FUNCTION *) calloc (1, sizeof(CS_FUNCTION));
+  if (csf == NULL)
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register function %s", funcname);
+  csf->name = strdup(funcname);
+  if (csf->name == NULL)
+  {
+    free(csf);
+    return nerr_raise(NERR_NOMEM, "Unable to allocate memory to register function %s", funcname);
+  }
+  csf->function = function;
+  csf->n_args = n_args;
+  csf->next = parse->functions;
+  parse->functions = csf;
+
+  return STATUS_OK;
+}
+
+static NEOERR * _builtin_len(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
+{
+  HDF *obj;
+  int count = 0;
+
+  result->op_type = CS_TYPE_NUM;
+  result->n = 0;
+
+  if (args->op_type & CS_TYPE_VAR)
+  {
+    obj = var_lookup_obj (parse, args->s);
+    if (obj != NULL)
+    {
+      obj = hdf_obj_child(obj);
+      while (obj != NULL)
+      {
+	count++;
+	obj = hdf_obj_next(obj);
+      }
+    }
+    result->n = count;
+  }
+  else if (args->op_type & CS_TYPE_STRING)
+  {
+    result->n = strlen(args->s);
+  }
+  return STATUS_OK;
+}
+
+static NEOERR * _str_func_wrapper (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
+{
+  NEOERR *err;
+  char *s;
+
+  if (args->op_type & (CS_TYPE_VAR | CS_TYPE_STRING))
+  {
+    result->op_type = CS_TYPE_STRING;
+    result->n = 0;
+
+    s = arg_eval(parse, args);
+    if (s)
+    {
+      err = csf->str_func(s, &(result->s));
+      if (err) return nerr_pass(err);
+      result->alloc = 1;
+    }
+  }
+  else
+  {
+    result->op_type = args->op_type;
+    result->n = args->n;
+    result->s = args->s;
+    result->alloc = args->alloc;
+    args->alloc = 0;
+  }
+  return STATUS_OK;
+}
+
+NEOERR *cs_register_strfunc(CSPARSE *parse, char *funcname, CSSTRFUNC str_func)
+{
+  NEOERR *err;
+
+  err = _register_function(parse, funcname, 1, _str_func_wrapper);
+  if (err) return nerr_pass(err);
+  parse->functions->str_func = str_func;
+
+  return STATUS_OK;
+}
+
+
+/* **** CS Initialize/Destroy ************************************ */
+
+NEOERR *cs_init (CSPARSE **parse, HDF *hdf)
+{
+  NEOERR *err = STATUS_OK;
+  CSPARSE *my_parse;
+  STACK_ENTRY *entry;
+
+  err = nerr_init();
+  if (err != STATUS_OK) return nerr_pass (err);
+
+  my_parse = (CSPARSE *) calloc (1, sizeof (CSPARSE));
+  if (my_parse == NULL)
+    return nerr_raise (NERR_NOMEM, "Unable to allocate memory for CSPARSE");
+
+
+  err = uListInit (&(my_parse->stack), 10, 0);
+  if (err != STATUS_OK)
+  {
+    free(my_parse);
+    return nerr_pass(err);
+  }
+  err = uListInit (&(my_parse->alloc), 10, 0);
+  if (err != STATUS_OK)
+  {
+    free(my_parse);
+    return nerr_pass(err);
+  }
+  err = alloc_node (&(my_parse->tree));
+  if (err != STATUS_OK)
+  {
+    cs_destroy (&my_parse);
+    return nerr_pass(err);
+  }
+  my_parse->current = my_parse->tree;
+  my_parse->next = &(my_parse->current->next);
+
+  entry = (STACK_ENTRY *) calloc (1, sizeof (STACK_ENTRY));
+  if (entry == NULL)
+  {
+    cs_destroy (&my_parse);
+    return nerr_raise (NERR_NOMEM, 
+	"Unable to allocate memory for stack entry");
+  }
+  entry->state = ST_GLOBAL;
+  entry->tree = my_parse->current;
+  entry->location = 0;
+  err = uListAppend(my_parse->stack, entry);
+  if (err != STATUS_OK) {
+    free (entry);
+    cs_destroy(&my_parse);
+    return nerr_pass(err);
+  }
+  err = _register_function(my_parse, "len", 1, _builtin_len);
+  if (err)
+  {
+    cs_destroy(&my_parse);
+    return nerr_pass(err);
+  }
+  my_parse->hdf = hdf;
+
+  *parse = my_parse;
+  return STATUS_OK;
+}
+
+void cs_destroy (CSPARSE **parse)
+{
+  CSPARSE *my_parse = *parse;
+
+  if (my_parse == NULL)
+    return;
+
+  uListDestroy (&(my_parse->stack), ULIST_FREE);
+  uListDestroy (&(my_parse->alloc), ULIST_FREE);
+
+  dealloc_macro(&my_parse->macros);
+  dealloc_node(&(my_parse->tree));
+  dealloc_function(&(my_parse->functions));
+
+  free(my_parse);
+  *parse = NULL;
+}
+
+/* **** CS Debug Dumps ******************************************** */
 
 static NEOERR *dump_node (CSPARSE *parse, CSTREE *node, int depth, void *ctx, 
     CSOUTFUNC cb, char *buf, int blen)
diff -ur clearsilver-0.6.2/rules.mk clearsilver-0.7.0/rules.mk
--- clearsilver-0.6.2/rules.mk	Tue Apr 30 20:05:14 2002
+++ clearsilver-0.7.0/rules.mk	Fri Jun 14 17:29:59 2002
@@ -18,7 +18,7 @@
 DB2_INC = -I$(HOME)/src/db-2.7.7/dist
 DB2_LIB = -L$(HOME)/src/db-2.7.7/dist -ldb
 endif
-PYTHON_INC = -I/neo/opt/include/python2.1 -I/neo/opt/include/python2.2
+PYTHON_INC = -I/neo/opt/include/python2.2
 
 ## Programs
 MKDIR      = mkdir -p
diff -ur clearsilver-0.6.2/util/neo_hdf.c clearsilver-0.7.0/util/neo_hdf.c
--- clearsilver-0.6.2/util/neo_hdf.c	Fri May 10 12:39:59 2002
+++ clearsilver-0.7.0/util/neo_hdf.c	Mon May 20 12:39:52 2002
@@ -478,7 +478,12 @@
       free(hdf->value);
       hdf->value = NULL;
     }
-    if (dup)
+    if (value == NULL)
+    {
+      hdf->alloc_value = 0;
+      hdf->value = NULL;
+    }
+    else if (dup)
     {
       hdf->alloc_value = 1;
       hdf->value = strdup(value);
@@ -528,6 +533,7 @@
       {
 	err = _alloc_hdf (&hp, n, x, value, dup, wf, hdf->top);
 	if (link) hp->link = 1;
+	else hp->link = 0;
 	hp->attr = attr;
       }
       if (err != STATUS_OK)
@@ -554,7 +560,12 @@
 	free(hp->value);
 	hp->value = NULL;
       }
-      if (dup)
+      if (value == NULL)
+      {
+	hp->alloc_value = 0;
+	hp->value = NULL;
+      }
+      else if (dup)
       {
 	hp->alloc_value = 1;
 	hp->value = strdup(value);
@@ -568,6 +579,7 @@
 	hp->value = value;
       }
       if (link) hp->link = 1;
+      else hp->link = 0;
     }
     if (s == NULL)
       break;
@@ -1274,6 +1286,17 @@
 	if (err != STATUS_OK)
 	  return nerr_pass_ctx(err, "In String %d", *line);
       }
+      else if (s[0] == ':' && s[1] == '=') /* copy */
+      {
+	*s = '\0';
+	name = neos_strip(name);
+	s+=2;
+	value = neos_strip(s);
+	value = hdf_get_value(hdf->top, value, "");
+	err = _set_value (hdf, name, value, 1, 1, 0, attr);
+	if (err != STATUS_OK)
+	  return nerr_pass_ctx(err, "In string %d", *line);
+      }
       else if (s[0] == ':') /* link */
       {
 	*s = '\0';
@@ -1393,14 +1416,17 @@
   STRING str;
   HDF *lower;
   HDF_ATTR *attr = NULL;
-  char buf[4096];
   char *s;
   char *name, *value;
   int l;
 
   string_init(&str);
   err = string_readline(&str, fp);
-  if (err) return nerr_pass(err);
+  if (err) 
+  {
+    string_clear(&str);
+    return nerr_pass(err);
+  }
   while (str.len != 0)
   {
     attr = NULL;
@@ -1419,7 +1445,10 @@
       }
       err = hdf_read_file(hdf, name);
       if (err != STATUS_OK) 
+      {
+	string_clear(&str);
 	return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+      }
     }
     else if (s[0] == '#')
     {
@@ -1430,10 +1459,13 @@
       s = neos_strip(s);
       if (strcmp(s, "}"))
       {
-	return nerr_raise(NERR_PARSE, 
+	err = nerr_raise(NERR_PARSE, 
 	    "[%s:%d] Trailing garbage on line following }: %s", path, *line,
-	    buf);
+	    str.buf);
+	string_clear(&str);
+	return err;
       }
+      string_clear(&str);
       return STATUS_OK;
     }
     else if (s[0])
@@ -1449,7 +1481,11 @@
 	name = neos_strip(name);
 	s++;
 	err = parse_attr(&s, &attr);
-	if (err) return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	if (err) 
+	{
+	  string_clear(&str);
+	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	}
 	SKIPWS(s);
       }
       if (s[0] == '=') /* assignment */
@@ -1460,7 +1496,24 @@
 	value = neos_strip(s);
 	err = _set_value (hdf, name, value, 1, 1, 0, attr);
 	if (err != STATUS_OK)
+	{
+	  string_clear(&str);
+	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	}
+      }
+      else if (s[0] == ':' && s[1] == '=') /* copy */
+      {
+	*s = '\0';
+	name = neos_strip(name);
+	s+=2;
+	value = neos_strip(s);
+	value = hdf_get_value(hdf->top, value, "");
+	err = _set_value (hdf, name, value, 1, 1, 0, attr);
+	if (err != STATUS_OK)
+	{
+	  string_clear(&str);
 	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	}
       }
       else if (s[0] == ':') /* link */
       {
@@ -1470,7 +1523,10 @@
 	value = neos_strip(s);
 	err = _set_value (hdf, name, value, 1, 1, 1, attr);
 	if (err != STATUS_OK)
+	{
+	  string_clear(&str);
 	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	}
       }
       else if (s[0] == '{') /* deeper */
       {
@@ -1481,7 +1537,10 @@
 	{
 	  err = _set_value (hdf, name, NULL, 1, 1, 0, attr);
 	  if (err != STATUS_OK) 
+	  {
+	    string_clear(&str);
 	    return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	  }
 	  lower = hdf_get_obj (hdf, name);
 	}
 	else
@@ -1490,7 +1549,10 @@
 	}
 	err = hdf_read_file_fp(lower, fp, path, line);
 	if (err != STATUS_OK) 
+	{
+	  string_clear(&str);
 	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
+	}
 	if (feof(fp)) break;
       }
       else if (s[0] == '<' && s[1] == '<') /* multi-line assignment */
@@ -1506,14 +1568,21 @@
 	value = neos_strip(s);
 	l = strlen(value);
 	if (l == 0)
-	  return nerr_raise(NERR_PARSE, 
+	{
+	  err = nerr_raise(NERR_PARSE, 
 	      "[%s:%d] No multi-assignment terminator given: %s", path, *line, 
-	      buf);
+	      str.buf);
+	  string_clear(&str);
+	  return err;
+	}
 	m = (char *) malloc (mmax * sizeof(char));
 	if (m == NULL)
+	{
+	  string_clear(&str);
 	  return nerr_raise(NERR_NOMEM, 
 	    "[%s:%d] Unable to allocate memory for multi-line assignment to %s",
 	    path, *line, name);
+	}
 	while (fgets(m+msize, mmax-msize, fp) != NULL)
 	{
 	  if (!strncmp(value, m+msize, l) && (m[msize+l] == '\r' || m[msize+l] == '\n'))
@@ -1527,15 +1596,19 @@
 	    mmax += 128;
 	    m = (char *) realloc (m, mmax * sizeof(char));
 	    if (m == NULL)
+	    {
+	      string_clear(&str);
 	      return nerr_raise(NERR_NOMEM, 
 		  "[%s:%d] Unable to allocate memory for multi-line assignment to %s: size=%d",
 		  path, *line, name, mmax);
+	    }
 	  }
 	}
 	err = _set_value (hdf, name, m, 0, 1, 0, attr);
 	if (err != STATUS_OK)
 	{
 	  free (m);
+	  string_clear(&str);
 	  return nerr_pass_ctx(err, "In file %s:%d", path, *line);
 	}
 	/* count the newlines so we know what line we're on... +1 for EOM */
@@ -1543,14 +1616,22 @@
       }
       else
       {
-	return nerr_raise(NERR_PARSE, "[%s:%d] Unable to parse line %s",
-	    path, *line, buf);
+	err = nerr_raise(NERR_PARSE, "[%s:%d] Unable to parse line %s",
+	    path, *line, str.buf);
+	string_clear(&str);
+	return err;
       }
     }
     str.len = 0;
     err = string_readline(&str, fp);
-    if (err) return nerr_pass(err);
+    /* ne_warn("string buf len is %d", str.len); */
+    if (err) 
+    {
+      string_clear(&str);
+      return nerr_pass(err);
+    }
   }
+  string_clear(&str);
   return STATUS_OK;
 }
 
diff -ur clearsilver-0.6.2/util/test/Makefile clearsilver-0.7.0/util/test/Makefile
--- clearsilver-0.6.2/util/test/Makefile	Thu Apr 18 12:24:55 2002
+++ clearsilver-0.7.0/util/test/Makefile	Mon May 20 12:40:43 2002
@@ -10,6 +10,10 @@
 HDFTEST_SRC = hdftest.c
 HDFTEST_OBJ = $(HDFTEST_SRC:%.c=%.o)
 
+HDFLOADTEST_EXE = hdfloadtest
+HDFLOADTEST_SRC = hdfloadtest.c
+HDFLOADTEST_OBJ = $(HDFLOADTEST_SRC:%.c=%.o)
+
 LISTDIRTEST_EXE = listdir_test
 LISTDIRTEST_SRC = listdir_test.c
 LISTDIRTEST_OBJ = $(LISTDIRTEST_SRC:%.c=%.o)
@@ -22,12 +26,16 @@
 CFLAGS += -I$(NEOTONIC_ROOT)/util
 LIBS += -L$(LIB_DIR) -lneo_utl 
 
-TARGETS = $(HDFTEST_EXE) $(LISTDIRTEST_EXE) $(HDFCOPYTEST_EXE)
+TARGETS = $(HDFTEST_EXE) $(LISTDIRTEST_EXE) $(HDFCOPYTEST_EXE) \
+	$(HDFLOADTEST_EXE)
 
 all: $(TARGETS)
 
 $(HDFTEST_EXE): $(HDFTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(HDFTEST_OBJ) $(LIBS)
+
+$(HDFLOADTEST_EXE): $(HDFLOADTEST_OBJ) $(NTR_LIB)
+	$(LD) $@ $(HDFLOADTEST_OBJ) $(LIBS)
 
 $(LISTDIRTEST_EXE): $(LISTDIRTEST_OBJ) $(NTR_LIB)
 	$(LD) $@ $(LISTDIRTEST_OBJ) $(LIBS)
