/*
 * Copyright 2001-2004 Brandon Long
 * All Rights Reserved.
 *
 * ClearSilver Templating System
 *
 * This code is made available under the terms of the ClearSilver License.
 * http://www.clearsilver.net/license.hdf
 *
 */

/*
 * TODO: there is some really ugly pseudo reference counting in here
 * for allocation of temporary strings (and passing references).  See the alloc
 * member of various structs for details.  We should move this to an arena
 * allocator so we can just allocate whenever we need to and just clean up
 * all the allocation at the end (may require two arenas: one for parese and
 * one for render)
 */

#include "cs_config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <limits.h>
#include <stdarg.h>

#ifdef ENABLE_GETTEXT
#include <libintl.h>
#endif

#include "util/neo_misc.h"
#include "util/neo_err.h"
#include "util/neo_files.h"
#include "util/neo_str.h"
#include "util/ulist.h"
#include "cs.h"

/* turn on some debug output for expressions */
#define DEBUG_EXPR_PARSE 0
#define DEBUG_EXPR_EVAL 0

typedef enum
{
  ST_SAME = 0,
  ST_GLOBAL = 1<<0,
  ST_IF = 1<<1,
  ST_ELSE = 1<<2,
  ST_EACH = 1<<3,
  ST_WITH = 1<<4,
  ST_POP = 1<<5,
  ST_DEF = 1<<6,
  ST_LOOP =  1<<7,
  ST_ALT = 1<<8,
  ST_ESCAPE = 1<<9,
} CS_STATE;

#define ST_ANYWHERE (ST_EACH | ST_WITH | ST_ELSE | ST_IF | ST_GLOBAL | ST_DEF | ST_LOOP | ST_ALT | ST_ESCAPE)

typedef struct _stack_entry
{
  CS_STATE state;
  NEOS_ESCAPE escape;
  CSTREE *tree;
  CSTREE *next_tree;
  int num_local;
  int location;
} STACK_ENTRY;

static NEOERR *literal_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *literal_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *name_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *name_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *var_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *var_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *evar_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *lvar_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *lvar_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *if_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *if_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *else_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *elif_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *endif_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *each_with_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *each_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *with_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *end_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *include_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *linclude_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *linclude_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *def_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *skip_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *call_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *call_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *set_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *set_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *loop_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *loop_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *alt_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *alt_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);
static NEOERR *escape_parse (CSPARSE *parse, int cmd, char *arg);
static NEOERR *escape_eval (CSPARSE *parse, CSTREE *node, CSTREE **next);

static NEOERR *render_node (CSPARSE *parse, CSTREE *node);
static NEOERR *cs_init_internal (CSPARSE **parse, HDF *hdf, CSPARSE *parent);
static int rearrange_for_call(CSARG **args);

typedef struct _cmds
{
  char *cmd;
  int cmdlen;
  CS_STATE allowed_state;
  CS_STATE next_state;
  NEOERR* (*parse_handler)(CSPARSE *parse, int cmd, char *arg);
  NEOERR* (*eval_handler)(CSPARSE *parse, CSTREE *node, CSTREE **next);
  int has_arg;
} CS_CMDS;

CS_CMDS Commands[] = {
  {"literal", sizeof("literal")-1, ST_ANYWHERE,     ST_SAME,
    literal_parse, literal_eval, 0},
  {"name",     sizeof("name")-1,     ST_ANYWHERE,     ST_SAME,
    name_parse, name_eval,     1},
  {"var",     sizeof("var")-1,     ST_ANYWHERE,     ST_SAME,
    var_parse, var_eval,     1},
  {"uvar",     sizeof("uvar")-1,     ST_ANYWHERE,     ST_SAME,
    var_parse, var_eval,     1},
  {"evar",    sizeof("evar")-1,    ST_ANYWHERE,     ST_SAME,
    evar_parse, skip_eval,    1},
  {"lvar",    sizeof("lvar")-1,    ST_ANYWHERE,     ST_SAME,
    lvar_parse, lvar_eval,    1},
  {"if",      sizeof("if")-1,      ST_ANYWHERE,     ST_IF,
    if_parse, if_eval,      1},
  {"else",    sizeof("else")-1,    ST_IF,           ST_POP | ST_ELSE,
    else_parse, skip_eval,    0},
  {"elseif",  sizeof("elseif")-1,  ST_IF,           ST_SAME,
    elif_parse, if_eval,   1},
  {"elif",    sizeof("elif")-1,    ST_IF,           ST_SAME,
    elif_parse, if_eval,   1},
  {"/if",     sizeof("/if")-1,     ST_IF | ST_ELSE, ST_POP,
    endif_parse, skip_eval,   0},
  {"each",    sizeof("each")-1,    ST_ANYWHERE,     ST_EACH,
    each_with_parse, each_eval,    1},
  {"/each",   sizeof("/each")-1,   ST_EACH,         ST_POP,
    end_parse, skip_eval, 0},
  {"with",    sizeof("each")-1,    ST_ANYWHERE,     ST_WITH,
    each_with_parse, with_eval,    1},
  {"/with",   sizeof("/with")-1,   ST_WITH,         ST_POP,
    end_parse, skip_eval, 0},
  {"include", sizeof("include")-1, ST_ANYWHERE,     ST_SAME,
    include_parse, skip_eval, 1},
  {"linclude", sizeof("linclude")-1, ST_ANYWHERE,     ST_SAME,
    linclude_parse, linclude_eval, 1},
  {"def",     sizeof("def")-1,     ST_ANYWHERE,     ST_DEF,
    def_parse, skip_eval, 1},
  {"/def",    sizeof("/def")-1,    ST_DEF,          ST_POP,
    end_parse, skip_eval, 0},
  {"call",    sizeof("call")-1,    ST_ANYWHERE,     ST_SAME,
    call_parse, call_eval, 1},
  {"set",    sizeof("set")-1,    ST_ANYWHERE,     ST_SAME,
    set_parse, set_eval, 1},
  {"loop",    sizeof("loop")-1,    ST_ANYWHERE,     ST_LOOP,
    loop_parse, loop_eval, 1},
  {"/loop",    sizeof("/loop")-1,    ST_LOOP,     ST_POP,
    end_parse, skip_eval, 1},
  {"alt",    sizeof("alt")-1,    ST_ANYWHERE,     ST_ALT,
    alt_parse, alt_eval, 1},
  {"/alt",    sizeof("/alt")-1,    ST_ALT,     ST_POP,
    end_parse, skip_eval, 1},
  {"escape",    sizeof("escape")-1,    ST_ANYWHERE,     ST_ESCAPE,
    escape_parse, escape_eval, 1},
  {"/escape",    sizeof("/escape")-1,    ST_ESCAPE,     ST_POP,
    end_parse, skip_eval, 1},
  {NULL},
};

/* Possible Config.VarEscapeMode values */
typedef struct _escape_modes
{
  char *mode; /* Add space for NUL */
  NEOS_ESCAPE context; /* Context of the name */
} CS_ESCAPE_MODES;

CS_ESCAPE_MODES EscapeModes[] = {
  {"none", NEOS_ESCAPE_NONE},
  {"html", NEOS_ESCAPE_HTML},
  {"js",   NEOS_ESCAPE_SCRIPT},
  {"url",  NEOS_ESCAPE_URL},
  {NULL},
};


/* **** CS alloc/dealloc ******************************************** */

static int NodeNumber = 0;

static void init_node_pos(CSTREE *node, CSPARSE *parse) 
{  
  CS_POSITION *pos = &parse->pos;
  char *data;

  if (parse->offset < pos->cur_offset) {
    /* Oops, we went backwards in file, is this an error? */
    node->linenum = -1;
    node->colnum = parse->offset;
    return;
  }

  /* Start counting from 1 not 0 */
  if (pos->line == 0) pos->line = 1;
  if (pos->col == 0) pos->col = 1;

  if (parse->context == NULL) {
    /* Not in a file */
    node->fname = NULL;
  }
  else {
    node->fname = strdup(parse->context);
    if (node->fname == NULL) {
      /* malloc error, cannot proceed */
      node->linenum = -1;
      return;
    }
  }

  data = parse->context_string;
  if (data == NULL) {
    node->linenum = -1;
    return;
  }
  
  while (pos->cur_offset < parse->offset) {
    if (data[pos->cur_offset] == '\n') {
      pos->line++;
      pos->col = 1;
    }
    else {
      pos->col++;
    }

    pos->cur_offset++;
  }
  
  node->linenum = pos->line;
  node->colnum = pos->col;
  
  return;

}

static NEOERR *alloc_node (CSTREE **node, CSPARSE *parse)
{
  CSTREE *my_node;

  *node = NULL;
  my_node = (CSTREE *) calloc (1, sizeof (CSTREE));
  if (my_node == NULL)
    return nerr_raise (NERR_NOMEM, "Unable to allocate memory for node");

  my_node->cmd = 0;
  my_node->node_num = NodeNumber++;

  *node = my_node;
  
  if (parse->audit_mode) {
    init_node_pos(my_node, parse);
  }
  return STATUS_OK;
}

/* TODO: make these deallocations linear and not recursive */
static void dealloc_arg (CSARG **arg)
{
  CSARG *p;

  if (*arg == NULL) return;
  p = *arg;
  if (p->expr1) dealloc_arg (&(p->expr1));
  if (p->expr2) dealloc_arg (&(p->expr2));
  if (p->next) dealloc_arg (&(p->next));

  if (p->argexpr) free(p->argexpr);

  free(p);
  *arg = NULL;
}

static void dealloc_node (CSTREE **node)
{
  CSTREE *my_node;

  if (*node == NULL) return;
  my_node = *node;
  if (my_node->case_0) dealloc_node (&(my_node->case_0));
  if (my_node->case_1) dealloc_node (&(my_node->case_1));
  if (my_node->next) dealloc_node (&(my_node->next));
  if (my_node->vargs) dealloc_arg (&(my_node->vargs));
  if (my_node->arg1.expr1) dealloc_arg (&(my_node->arg1.expr1));
  if (my_node->arg1.expr2) dealloc_arg (&(my_node->arg1.expr2));
  if (my_node->arg1.next) dealloc_arg (&(my_node->arg1.next));
  if (my_node->arg2.expr1) dealloc_arg (&(my_node->arg2.expr1));
  if (my_node->arg2.expr2) dealloc_arg (&(my_node->arg2.expr2));
  if (my_node->arg2.next) dealloc_arg (&(my_node->arg2.next));

  if (my_node->arg1.argexpr) free(my_node->arg1.argexpr);
  if (my_node->arg2.argexpr) free(my_node->arg2.argexpr);
  if (my_node->fname) free(my_node->fname);

  free(my_node);
  *node = NULL;
}

static void dealloc_macro (CS_MACRO **macro)
{
  CS_MACRO *my_macro;

  if (*macro == NULL) return;
  my_macro = *macro;
  if (my_macro->name) free (my_macro->name);
  if (my_macro->args) dealloc_arg (&(my_macro->args));
  if (my_macro->next) dealloc_macro (&(my_macro->next));
  free (my_macro);
  *macro = NULL;
}

static void dealloc_function (CS_FUNCTION **csf)
{
  CS_FUNCTION *my_csf;

  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 (CSPARSE *parse, char *buf, int x, int len)
{
  char *p;
  int ws_index = 2+parse->taglen;

  while (x < len)
  {
    p = strchr (&(buf[x]), '<');
    if (p == NULL) return -1;
    if (p[1] == '?' && !strncasecmp(&p[2], parse->tag, parse->taglen) &&
	(p[ws_index] == ' ' || p[ws_index] == '\n' || p[ws_index] == '\t' || p[ws_index] == '\r'))
      /*
    if (p[1] && p[1] == '?' &&
	p[2] && (p[2] == 'C' || p[2] == 'c') &&
	p[3] && (p[3] == 'S' || p[3] == 's') &&
	p[4] && (p[4] == ' ' || p[4] == '\n' || p[4] == '\t' || p[4] == '\r'))
	*/
    {
      return p - buf;
    }
    x = p - buf + 1;
  }
  return -1;
}

static NEOERR *_store_error (CSPARSE *parse, NEOERR *err) 
{
  CS_ERROR *ptr;
  CS_ERROR *node;

  node = (CS_ERROR *) calloc(1, sizeof(CS_ERROR));
  if (node == NULL) 
  {
    return nerr_raise (NERR_NOMEM,
        "Unable to allocate memory for error entry");
  }

  node->err = err;

  if (parse->err_list == NULL)
  {
    parse->err_list = node;
    return STATUS_OK;
  }

  ptr = parse->err_list;
  while (ptr->next != NULL) 
    ptr = ptr->next;

  ptr->next = node;
  return STATUS_OK;
      
}

NEOERR *cs_parse_file (CSPARSE *parse, const char *path)
{
  NEOERR *err;
  char *ibuf;
  const char *save_context;
  int save_infile;
  char fpath[_POSIX_PATH_MAX];
  CS_POSITION pos;

  if (path == NULL)
    return nerr_raise (NERR_ASSERT, "path is NULL");

  if (parse->fileload)
  {
    err = parse->fileload(parse->fileload_ctx, parse->hdf, path, &ibuf);
  }
  else
  {
    if (path[0] != '/')
    {
      err = hdf_search_path (parse->hdf, path, fpath);
      if (parse->global_hdf && nerr_handle(&err, NERR_NOT_FOUND))
        err = hdf_search_path(parse->global_hdf, path, fpath);
      if (err != STATUS_OK) return nerr_pass(err);
      path = fpath;
    }

    err = ne_load_file (path, &ibuf);
  }
  if (err) return nerr_pass (err);

  save_context = parse->context;
  parse->context = path;
  save_infile = parse->in_file;
  parse->in_file = 1;

  if (parse->audit_mode) {
    /* Save previous position before parsing the new file */
    memcpy(&pos, &parse->pos, sizeof(CS_POSITION));
    
    parse->pos.line = 0;
    parse->pos.col = 0;
    parse->pos.cur_offset = 0;
  }

  err = cs_parse_string(parse, ibuf, strlen(ibuf));

  if (parse->audit_mode) {
    memcpy(&parse->pos, &pos, sizeof(CS_POSITION));
  }

  parse->in_file = save_infile;
  parse->context = save_context;

  return nerr_pass(err);
}

static char *find_context (CSPARSE *parse, int offset, char *buf, size_t blen)
{
  FILE *fp;
  int dump_err = 1;
  char line[256];
  int count = 0;
  int lineno = 0;
  char *data;

  if (offset == -1) offset = parse->offset;

  do
  {
    if (parse->in_file && parse->context)
    {
      /* Open the file and find which line we're on */

      fp = fopen(parse->context, "r");
      if (fp == NULL) {
	ne_warn("Unable to open context %s", parse->context);
	break;
      }
      while (fgets(line, sizeof(line), fp) != NULL)
      {
	count += strlen(line);
	if (strchr(line, '\n') != NULL)
	  lineno++;
	if (count > offset) break;
      }
      fclose (fp);
      snprintf (buf, blen, "[%s:%d]", parse->context, lineno);
    }
    else
    {
      data = parse->context_string;
      if (data != NULL)
      {
	lineno = 1;
	while (count < offset)
	{
	  if (data[count++] == '\n') lineno++;
	}
	if (parse->context)
	  snprintf (buf, blen, "[%s:~%d]", parse->context, lineno);
	else
	  snprintf (buf, blen, "[lineno:~%d]", lineno);
      }
      else
      {
	if (parse->context)
	  snprintf (buf, blen, "[%s:%d]", parse->context, offset);
	else
	  snprintf (buf, blen, "[offset:%d]", offset);
      }
    }
    dump_err = 0;
  } while (0);
  if (dump_err)
  {
    if (parse->context)
      snprintf (buf, blen, "[-E- %s:%d]", parse->context, offset);
    else
      snprintf (buf, blen, "[-E- offset:%d]", offset);
  }

  return buf;
}

static char *expand_state (CS_STATE state)
{
  static char buf[256];

  if (state & ST_GLOBAL)
    return "GLOBAL";
  else if (state & ST_IF)
    return "IF";
  else if (state & ST_ELSE)
    return "ELSE";
  else if (state & ST_EACH)
    return "EACH";
  else if (state & ST_WITH)
    return "WITH";
  else if (state & ST_DEF)
    return "DEF";
  else if (state & ST_LOOP)
    return "LOOP";
  else if (state & ST_ALT)
    return "ALT";
  else if (state & ST_ESCAPE)
    return "ESCAPE";

  snprintf(buf, sizeof(buf), "Unknown state %d", state);
  return buf;
}

NEOERR *cs_parse_string (CSPARSE *parse, char *ibuf, size_t ibuf_len)
{
  NEOERR *err = STATUS_OK;
  STACK_ENTRY *entry, *current_entry;
  char *p;
  char *token;
  int done = 0;
  int i, n;
  char *arg;
  int initial_stack_depth;
  int initial_offset;
  char *initial_context;
  char tmp[256];

  err = uListAppend(parse->alloc, ibuf);
  if (err)
  {
    free (ibuf);
    return nerr_pass (err);
  }

  initial_stack_depth = uListLength(parse->stack);
  initial_offset = parse->offset;
  initial_context = parse->context_string;

  parse->offset = 0;
  parse->context_string = ibuf;
  while (!done)
  {
    /* Stage 1: Find <?cs starter */
    i = find_open_delim (parse, ibuf, parse->offset, ibuf_len);
    if (i >= 0)
    {
      ibuf[i] = '\0';
      /* Create literal with data up until start delim */
      /* ne_warn ("literal -> %d-%d", parse->offset, i);  */
      err = (*(Commands[0].parse_handler))(parse, 0, &(ibuf[parse->offset]));
      /* skip delim */
      token = &(ibuf[i+3+parse->taglen]);
      while (*token && isspace(*token)) token++;

      p = strstr (token, "?>");
      if (p == NULL)
      {
	return nerr_raise (NERR_PARSE, "%s Missing end ?> at %s",
	    find_context(parse, i, tmp, sizeof(tmp)), &(ibuf[parse->offset]));
      }
      *p = '\0';
      if (strstr (token, "<?") != NULL)
      {
	return nerr_raise (NERR_PARSE, "%s Missing end ?> at %s",
	    find_context(parse, i, tmp, sizeof(tmp)),
	    token);
      }
      parse->offset = p - ibuf + 2;
      if (token[0] != '#') /* handle comments */
      {
	for (i = 1; Commands[i].cmd; i++)
	{
	  n = Commands[i].cmdlen;
	  if (!strncasecmp(token, Commands[i].cmd, n))
	  {
	    if ((Commands[i].has_arg && ((token[n] == ':') || (token[n] == '!')))
		|| (token[n] == ' ' || token[n] == '\0' || token[n] == '\r' || token[n] == '\n'))
	    {
	      err = uListGet (parse->stack, -1, (void *)&entry);
	      if (err != STATUS_OK) goto cs_parse_done;
	      if (!(Commands[i].allowed_state & entry->state))
	      {
		return nerr_raise (NERR_PARSE,
		    "%s Command %s not allowed in %s", Commands[i].cmd,
		    find_context(parse, -1, tmp, sizeof(tmp)),
		    expand_state(entry->state));
	      }
	      if (Commands[i].has_arg)
	      {
		/* Need to parse out arg */
		arg = &token[n];
		err = (*(Commands[i].parse_handler))(parse, i, arg);
	      }
	      else
	      {
		err = (*(Commands[i].parse_handler))(parse, i, NULL);
	      }
	      if (err != STATUS_OK) goto cs_parse_done;
	      if (Commands[i].next_state & ST_POP)
	      {
                void *ptr;
		err = uListPop(parse->stack, &ptr);
		if (err != STATUS_OK) goto cs_parse_done;
                entry = (STACK_ENTRY *)ptr;
		if (entry->next_tree)
		  parse->current = entry->next_tree;
		else
		  parse->current = entry->tree;
		free(entry);
	      }
	      if ((Commands[i].next_state & ~ST_POP) != ST_SAME)
	      {
		entry = (STACK_ENTRY *) calloc (1, sizeof (STACK_ENTRY));
		if (entry == NULL)
		  return nerr_raise (NERR_NOMEM,
		      "%s Unable to allocate memory for stack entry",
		      find_context(parse, -1, tmp, sizeof(tmp)));
		entry->state = Commands[i].next_state;
		entry->tree = parse->current;
		entry->location = parse->offset;
		/* Set the new stack escape context to the parent one */
		err = uListGet (parse->stack, -1, (void *)&current_entry);
		if (err != STATUS_OK) {
		  free (entry);
		  goto cs_parse_done;
		}
		entry->escape = current_entry->escape;
		/* Get the future escape context from parse because when
		 * we parse "escape", the new stack has not yet been established.
		 */
		entry->escape = parse->escaping.next_stack;
		parse->escaping.next_stack = parse->escaping.global_ctx;
		err = uListAppend(parse->stack, entry);
		if (err != STATUS_OK) {
		  free (entry);
		  goto cs_parse_done;
		}
	      }
	      break;
	    }
	  }
	}
	if (Commands[i].cmd == NULL)
	{
	  return nerr_raise (NERR_PARSE, "%s Unknown command %s",
	      find_context(parse, -1, tmp, sizeof(tmp)), token);
	}
      }
    }
    else
    {
      /* Create literal with all remaining data */
      err = (*(Commands[0].parse_handler))(parse, 0, &(ibuf[parse->offset]));
      done = 1;
    }
  }
  /* Should we check the parse stack here? */
  while (uListLength(parse->stack) > initial_stack_depth)
  {
    err = uListPop(parse->stack, (void *)&entry);
    if (err != STATUS_OK) goto cs_parse_done;
    if (entry->state & ~(ST_GLOBAL | ST_POP))
      return nerr_raise (NERR_PARSE, "%s Non-terminted %s clause",
	  find_context(parse, entry->location, tmp, sizeof(tmp)),
          expand_state(entry->state));
  }

cs_parse_done:
  parse->offset = initial_offset;
  parse->context_string = initial_context;
  parse->escaping.current = NEOS_ESCAPE_NONE;
  return nerr_pass(err);
}

static CS_LOCAL_MAP * lookup_map (CSPARSE *parse, char *name, char **rest)
{
  CS_LOCAL_MAP *map;
  char *c;

  /* This shouldn't happen, but it did once... */
  if (name == NULL) return NULL;
  map = parse->locals;
  c = strchr (name, '.');
  if (c != NULL) *c = '\0';
  *rest = c;
  while (map != NULL)
  {
    if (!strcmp (map->name, name))
    {
      if (c != NULL) *c = '.';
      return map;
    }
    map = map->next;
  }
  if (c != NULL) *c = '.';
  return NULL;
}

static HDF *var_lookup_obj (CSPARSE *parse, char *name)
{
  CS_LOCAL_MAP *map;
  char *c;
  HDF *ret_hdf;

  map = lookup_map (parse, name, &c);
  if (map && map->type == CS_TYPE_VAR)
  {
    if (c == NULL)
    {
      return map->h;
    }
    else
    {
      return hdf_get_obj (map->h, c+1);
    }
  }
  /* smarti:  Added support for global hdf under local hdf */
  /* return hdf_get_obj (parse->hdf, name); */
  ret_hdf = hdf_get_obj (parse->hdf, name);
  if (ret_hdf == NULL && parse->global_hdf != NULL) {
    ret_hdf = hdf_get_obj (parse->global_hdf, name);
  }
  return ret_hdf;
}

/* Ugh, I have to write the same walking code because I can't grab the
 * object for writing, as it might not exist... */
static NEOERR *var_set_value (CSPARSE *parse, char *name, char *value)
{
  CS_LOCAL_MAP *map;
  char *c;

  map = parse->locals;
  c = strchr (name, '.');
  if (c != NULL) *c = '\0';
  while (map != NULL)
  {
    if (!strcmp (map->name, name))
    {
      if (map->type == CS_TYPE_VAR)
      {
	if (c == NULL)
	{
          if (map->h == NULL) /* node didn't exist yet */
            return nerr_pass (hdf_set_value (parse->hdf, map->s, value));
          else
            return nerr_pass (hdf_set_value (map->h, NULL, value));
	}
	else
	{
	  *c = '.';
          if (map->h == NULL) /* node didn't exist yet */
          {
            NEOERR *err;
            char *mapped_name = sprintf_alloc("%s%s", map->s, c);
            if (mapped_name == NULL)
              return nerr_raise(NERR_NOMEM, "Unable to allocate memory to create mapped name");
            err = hdf_set_value(parse->hdf, mapped_name, value);
            free(mapped_name);
            return nerr_pass(err);
          }
	  return nerr_pass (hdf_set_value (map->h, c+1, value));
	}
      }
      else
      {
	if (c == NULL)
	{
	  char *tmp = NULL;
	  /* If this is a string, it might be what we're setting,
	   * ie <?cs set:value = value ?>
	   */
	  if (map->type == CS_TYPE_STRING && map->map_alloc)
	    tmp = map->s;
	  map->type = CS_TYPE_STRING;
	  map->map_alloc = 1;
	  map->s = strdup(value);
	  if (tmp != NULL) free(tmp);
	  if (map->s == NULL && value != NULL)
	    return nerr_raise(NERR_NOMEM,
		"Unable to allocate memory to set var");

	  return STATUS_OK;
	}
	else {
	  ne_warn("WARNING!! Trying to set sub element '%s' of local variable '%s' which doesn't map to an HDF variable, ignoring", c+1, map->name);
	  return STATUS_OK;
	}
      }
    }
    map = map->next;
  }
  if (c != NULL) *c = '.';
  return nerr_pass (hdf_set_value (parse->hdf, name, value));
}

static char *var_lookup (CSPARSE *parse, char *name)
{
  CS_LOCAL_MAP *map;
  char *c;
  char* retval;

  map = lookup_map (parse, name, &c);
  if (map)
  {
    if (map->type == CS_TYPE_VAR)
    {
      if (c == NULL)
      {
	return hdf_obj_value (map->h);
      }
      else
      {
	return hdf_get_value (map->h, c+1, NULL);
      }
    }
    /* Hmm, if c != NULL, they are asking for a sub member of something
     * which isn't a var... right now we ignore them, I don't know what
     * the right thing is */
    /* hmm, its possible now that they are getting a reference to a
     * string that will be deleted... where is it used? */
    else if (map->type == CS_TYPE_STRING)
    {
      return map->s;
    }
    else if (map->type == CS_TYPE_NUM)
    {
      char buf[40];
      if (map->s) return map->s;
      snprintf (buf, sizeof(buf), "%ld", map->n);
      map->s = strdup(buf);
      map->map_alloc = 1;
      return map->s;
    }
  }
  /* smarti:  Added support for global hdf under local hdf */
  /* return hdf_get_value (parse->hdf, name, NULL); */
  retval = hdf_get_value (parse->hdf, name, NULL);
  if (retval == NULL && parse->global_hdf != NULL) {
    retval = hdf_get_value (parse->global_hdf, name, NULL);
  }
  return retval;
}

long int var_int_lookup (CSPARSE *parse, char *name)
{
  char *vs;

  vs = var_lookup (parse, name);

  if (vs == NULL)
    return 0;
  else
    return atoi(vs);
}

typedef struct _token
{
  CSTOKEN_TYPE type;
  char *value;
  size_t len;
} CSTOKEN;

struct _simple_tokens
{
  BOOL two_chars;
  char *token;
  CSTOKEN_TYPE type;
} SimpleTokens[] = {
  { TRUE, "<=", CS_OP_LTE },
  { TRUE, ">=", CS_OP_GTE },
  { TRUE, "==", CS_OP_EQUAL },
  { 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_ADD },
  { FALSE, "-", CS_OP_SUB },
  { FALSE, "*", CS_OP_MULT },
  { FALSE, "/", CS_OP_DIV },
  { FALSE, "%", CS_OP_MOD },
  { FALSE, "(", CS_OP_LPAREN },
  { FALSE, ")", CS_OP_RPAREN },
  { FALSE, "[", CS_OP_LBRACKET },
  { FALSE, "]", CS_OP_RBRACKET },
  { FALSE, ".", CS_OP_DOT },
  { FALSE, ",", CS_OP_COMMA },
  { FALSE, NULL, 0 }
};

#define MAX_TOKENS 256

static NEOERR *parse_tokens (CSPARSE *parse, char *arg, CSTOKEN *tokens,
    int *used_tokens)
{
  char tmp[256];
  int ntokens = 0;
  int x;
  BOOL found;
  BOOL last_is_op = 1;
  char *p, *p2;
  char *expr = arg;

  while (arg && *arg != '\0')
  {
    while (*arg && isspace(*arg)) arg++;
    if (*arg == '\0') break;
    x = 0;
    found = FALSE;

    /* If we already saw an operator, and this is a +/-, assume its
     * a number */
    if (!(last_is_op && (*arg == '+' || *arg == '-')))
    {
      while ((found == FALSE) && SimpleTokens[x].token)
      {
	if (((SimpleTokens[x].two_chars == TRUE) &&
	      (*arg == SimpleTokens[x].token[0]) &&
	      (*(arg + 1) == SimpleTokens[x].token[1])) ||
	    ((SimpleTokens[x].two_chars == FALSE) &&
	     (*arg == SimpleTokens[x].token[0])))
	{
	  tokens[ntokens++].type = SimpleTokens[x].type;
	  found = TRUE;
	  arg++;
	  if (SimpleTokens[x].two_chars) arg++;
	}
	x++;
      }
      /* Another special case: RPAREN and RBRACKET can have another op
       * after it */
      if (found && !(tokens[ntokens-1].type == CS_OP_RPAREN || tokens[ntokens-1].type == CS_OP_RBRACKET))
	last_is_op = 1;
    }

    if (found == FALSE)
    {
      if (*arg == '#')
      {
        /* TODO: make # an operator and not syntax */
	arg++;
	tokens[ntokens].type = CS_TYPE_NUM;
	tokens[ntokens].value = arg;
	strtol(arg, &p, 0);
	if (p == arg)
	{
	  tokens[ntokens].type = CS_TYPE_VAR_NUM;
	  p = strpbrk(arg, "\"?<>=!#-+|&,)*/%[]( \t\r\n");
	  if (p == arg)
	    return nerr_raise (NERR_PARSE, "%s Missing varname/number after #: %s",
		find_context(parse, -1, tmp, sizeof(tmp)), arg);
	}
	if (p == NULL)
	  tokens[ntokens].len = strlen(arg);
	else
	  tokens[ntokens].len = p - arg;
	ntokens++;
	arg = p;
      }
      else if (*arg == '"')
      {
	arg++;
	tokens[ntokens].type = CS_TYPE_STRING;
	tokens[ntokens].value = arg;
	p = strchr (arg, '"');
	if (p == NULL)
	  return nerr_raise (NERR_PARSE, "%s Missing end of string: %s",
	      find_context(parse, -1, tmp, sizeof(tmp)), arg);
	tokens[ntokens].len = p - arg;
	ntokens++;
	arg = p + 1;
      }
      else if (*arg == '\'')
      {
	arg++;
	tokens[ntokens].type = CS_TYPE_STRING;
	tokens[ntokens].value = arg;
	p = strchr (arg, '\'');
	if (p == NULL)
	  return nerr_raise (NERR_PARSE, "%s Missing end of string: %s",
	      find_context(parse, -1, tmp, sizeof(tmp)), arg);
	tokens[ntokens].len = p - arg;
	ntokens++;
	arg = p + 1;
      }
      else if (*arg == '$')
      {
        /* TODO: make $ an operator and not syntax */
	arg++;
	tokens[ntokens].type = CS_TYPE_VAR;
	tokens[ntokens].value = arg;
	p = strpbrk(arg, "\"?<>=!#-+|&,)*/%[]( \t\r\n");
	if (p == arg)
	  return nerr_raise (NERR_PARSE, "%s Missing varname after $: %s",
	      find_context(parse, -1, tmp, sizeof(tmp)), arg);
	if (p == NULL)
	  tokens[ntokens].len = strlen(arg);
	else
	  tokens[ntokens].len = p - arg;
	ntokens++;
	arg = p;
      }
      else
      {
	tokens[ntokens].type = CS_TYPE_VAR;
	tokens[ntokens].value = arg;
	/* Special case for Dave: If this is entirely a number, treat it
	 * as one */
	strtol(arg, &p2, 0);
	p = strpbrk(arg, "\"?<>=!#-+|&,)*/%[]( \t\r\n");
	/* This is complicated because +/- is valid in a number, but not
	 * in a varname */
	if (p2 != arg && (p <= p2 || (p == NULL && *p2 == '\0')))
	{
	  tokens[ntokens].type = CS_TYPE_NUM;
	  tokens[ntokens].len = p2 - arg;
	  arg = p2;
	}
	else
	{
	  if (p == arg)
	    return nerr_raise (NERR_PARSE,
		"%s Var arg specified with no varname: %s",
		find_context(parse, -1, tmp, sizeof(tmp)), arg);
	  if (p == NULL)
	    tokens[ntokens].len = strlen(arg);
	  else
	    tokens[ntokens].len = p - arg;
	  arg = p;
	}
	ntokens++;
      }
      last_is_op = 0;
    }
    if (ntokens >= MAX_TOKENS)
	return nerr_raise (NERR_PARSE,
	    "%s Expression exceeds maximum number of tokens of %d: %s",
	    find_context(parse, -1, tmp, sizeof(tmp)), MAX_TOKENS, expr);
  }
  *used_tokens = ntokens;
  return STATUS_OK;
}

CSTOKEN_TYPE OperatorOrder[] = {
  CS_OP_COMMA,
  CS_OP_OR,
  CS_OP_AND,
  CS_OP_EQUAL | CS_OP_NEQUAL,
  CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE,
  CS_OP_ADD | CS_OP_SUB,
  CS_OP_MULT | CS_OP_DIV | CS_OP_MOD,
  CS_OP_NOT | CS_OP_EXISTS,
  CS_OP_LBRACKET | CS_OP_DOT | CS_OP_LPAREN,
  0
};

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 "<";
    case CS_OP_LTE: return "<=";
    case CS_OP_GT: return ">";
    case CS_OP_GTE: return ">=";
    case CS_OP_AND: return "&&";
    case CS_OP_OR: return "||";
    case CS_OP_ADD: return "+";
    case CS_OP_SUB: return "-";
    case CS_OP_MULT: return "*";
    case CS_OP_DIV: return "/";
    case CS_OP_MOD: return "%";
    case CS_OP_LPAREN: return "(";
    case CS_OP_RPAREN: return ")";
    case CS_OP_LBRACKET: return "[";
    case CS_OP_RBRACKET: return "]";
    case CS_OP_DOT : return ".";
    case CS_OP_COMMA : return ",";
    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";
    case CS_TYPE_MACRO: return more ? "MACRO" : "m";
    case CS_TYPE_FUNCTION: return more ? "FUNC" : "f";
    default: return "u";
  }
  return "u";
}

static char *token_list (CSTOKEN *tokens, int ntokens, char *buf, size_t buflen)
{
  char *p = buf;
  int i, t;
  char save;

  for (i = 0; i < ntokens && buflen > 0; i++)
  {
    if (tokens[i].value)
    {
      save = tokens[i].value[tokens[i].len];
      tokens[i].value[tokens[i].len] = '\0';
      t = snprintf(p, buflen, "%s%d:%s:'%s'", i ? "  ":"", i, expand_token_type(tokens[i].type, 0), tokens[i].value);
      tokens[i].value[tokens[i].len] = save;
    }
    else
    {
      t = snprintf(p, buflen, "%s%d:%s", i ? "  ":"", i, expand_token_type(tokens[i].type, 0));
    }
    if (t == -1 || t >= buflen) return buf;
    buflen -= t;
    p += t;
  }
  return buf;
}

static NEOERR *parse_expr2 (CSPARSE *parse, CSTOKEN *tokens, int ntokens, int lvalue, CSARG *arg)
{
  NEOERR *err = STATUS_OK;
  char tmp[256];
  char tmp2[256];
  int x, op;
  int m;

#if DEBUG_EXPR_PARSE
  fprintf(stderr, "%s\n", token_list(tokens, ntokens, tmp, sizeof(tmp)));
  for (x = 0; x < ntokens; x++)
  {
    fprintf (stderr, "%s ", expand_token_type(tokens[x].type, 0));
  }
  fprintf(stderr, "\n");
#endif

  /* Not quite sure what to do with this case... */
  if (ntokens == 0)
  {
    return nerr_raise (NERR_PARSE, "%s Bad Expression",
	find_context(parse, -1, tmp, sizeof(tmp)));
  }
  if (ntokens == 1)
  {
    x = 0;
    if (tokens[0].type & CS_TYPES)
    {
      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);
      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, 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, lvalue, arg->expr1);
    return nerr_pass(err);
  }
  */

  op = 0;
  while (OperatorOrder[op])
  {
    x = ntokens-1;
    while (x >= 0)
    {
      /* handle associative ops by skipping through the entire set here,
       * ie the whole thing is an expression that can't match a binary op */
      if (tokens[x].type & CS_OP_RPAREN)
      {
	m = 1;
	x--;
	while (x >= 0)
	{
	  if (tokens[x].type & CS_OP_RPAREN) m++;
	  if (tokens[x].type & CS_OP_LPAREN) m--;
	  if (m == 0) break;
	  x--;
	}
	if (m)
	  return nerr_raise (NERR_PARSE,
	      "%s Missing left parenthesis in expression",
	      find_context(parse, -1, tmp, sizeof(tmp)));
	/* if (x == 0) break; */
	/* x--; */
	/* we don't do an x-- here, because we are special casing the
	 * left bracket to be both an operator and an associative */
      }
      if (tokens[x].type & CS_OP_RBRACKET)
      {
	m = 1;
	x--;
	while (x >= 0)
	{
	  if (tokens[x].type & CS_OP_RBRACKET) m++;
	  if (tokens[x].type & CS_OP_LBRACKET) m--;
	  if (m == 0) break;
	  x--;
	}
	if (m)
	  return nerr_raise (NERR_PARSE,
	      "%s Missing left bracket in expression",
	      find_context(parse, -1, tmp, sizeof(tmp)));
	if (x == 0) break;
	/* we don't do an x-- here, because we are special casing the
	 * left bracket to be both an operator and an associative */
      }
      if (lvalue && !(tokens[x].type & CS_OPS_LVALUE))
      {
	return nerr_raise (NERR_PARSE,
	    "%s Invalid op '%s' in lvalue",
	    find_context(parse, -1, tmp, sizeof(tmp)),
	    expand_token_type(tokens[x].type, 0));
      }
      if (tokens[x].type & OperatorOrder[op])
      {
	if (tokens[x].type & CS_OPS_UNARY)
	{
	  if (x == 0)
	  {
	    arg->op_type = tokens[x].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)));
            if (tokens[x].type & CS_OP_LPAREN)
            {
              if (!(tokens[ntokens-1].type & CS_OP_RPAREN))
              {
                return nerr_raise (NERR_PARSE,
                                   "%s Missing right parenthesis in expression",
                                   find_context(parse, -1, tmp, sizeof(tmp)));
              }
              /* XXX: we might want to set lvalue to 0 here */
              /* -2 since we strip the RPAREN as well */
              err = parse_expr2(parse, tokens + 1, ntokens-2, lvalue, arg->expr1);
            }
            else
            {
              err = parse_expr2(parse, tokens + 1, ntokens-1, lvalue, arg->expr1);
            }
	    return nerr_pass(err);
	  }
	}
	else if (tokens[x].type == CS_OP_COMMA)
	{
	  /* Technically, comma should be a left to right, not right to
	   * left, so we're going to build up the arguments in reverse
	   * order... */
	  arg->op_type = tokens[x].type;
	  /* The actual argument is expr1 */
	  arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
	  /* The previous argument is next */
	  arg->next = (CSARG *) calloc (1, sizeof (CSARG));
	  if (arg->expr1 == NULL || arg->next == 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 + x + 1, ntokens-x-1, lvalue, arg->expr1);
	  if (err) return nerr_pass (err);
	  err = parse_expr2(parse, tokens, x, lvalue, arg->next);
	  if (err) return nerr_pass (err);
	  return STATUS_OK;
	}
	else
	{
	  arg->op_type = tokens[x].type;
	  arg->expr2 = (CSARG *) calloc (1, sizeof (CSARG));
	  arg->expr1 = (CSARG *) calloc (1, sizeof (CSARG));
	  if (arg->expr1 == NULL || arg->expr2 == NULL)
	    return nerr_raise (NERR_NOMEM,
		"%s Unable to allocate memory for expression",
		find_context(parse, -1, tmp, sizeof(tmp)));
	  if (tokens[x].type & CS_OP_LBRACKET)
	  {
            if (!(tokens[ntokens-1].type & CS_OP_RBRACKET))
            {
              return nerr_raise (NERR_PARSE,
                                 "%s Missing right bracket in expression",
                                 find_context(parse, -1, tmp, sizeof(tmp)));
            }
	    /* Inside of brackets, we don't limit to valid lvalue ops */
            /* -2 since we strip the RBRACKET as well */
	    err = parse_expr2(parse, tokens + x + 1, ntokens-x-2, 0, arg->expr2);
	  }
	  else
	  {
	    err = parse_expr2(parse, tokens + x + 1, ntokens-x-1, lvalue, arg->expr2);
	  }
	  if (err) return nerr_pass (err);
	  err = parse_expr2(parse, tokens, x, lvalue, arg->expr1);
	  if (err) return nerr_pass (err);
	  return STATUS_OK;
	}
      }
      x--;
    }
    op++;
  }

  /* Unary op against an entire expression */
  if ((tokens[0].type & CS_OPS_UNARY) && tokens[1].type == CS_OP_LPAREN &&
      tokens[ntokens-1].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, lvalue, 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, lvalue, arg->expr1);
    return nerr_pass(err);
  }

  /* function call */
  if ((tokens[0].type & CS_TYPE_VAR) && tokens[1].type == CS_OP_LPAREN &&
      tokens[ntokens-1].type == CS_OP_RPAREN)
  {
    CS_FUNCTION *csf;
    int nargs;

    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)));
    if (ntokens-3 > 0) {
      err = parse_expr2(parse, tokens + 2, ntokens-3, lvalue, arg->expr1);
      if (err) return nerr_pass(err);
    } else {
      free(arg->expr1);
      arg->expr1 = NULL;
    }
    nargs = rearrange_for_call(&(arg->expr1));
    if (nargs != arg->function->n_args)
    {
      return nerr_raise (NERR_PARSE,
	  "%s Incorrect number of arguments in call to %s, expected %d, got %d",
	  find_context(parse, -1, tmp, sizeof(tmp)), tokens[0].value,
	  arg->function->n_args, nargs);
    }
    return nerr_pass(err);
  }

  return nerr_raise (NERR_PARSE, "%s Bad Expression:%s",
      find_context(parse, -1, tmp, sizeof(tmp)),
      token_list(tokens, ntokens, tmp2, sizeof(tmp2)));
}

static NEOERR *parse_expr (CSPARSE *parse, char *arg, int lvalue, CSARG *expr)
{
  NEOERR *err;
  CSTOKEN tokens[MAX_TOKENS];
  int ntokens = 0;

  memset(tokens, 0, sizeof(CSTOKEN) * MAX_TOKENS);
  err = parse_tokens (parse, arg, tokens, &ntokens);
  if (err) return nerr_pass(err);

  if (parse->audit_mode) {
    /* Save the complete expression string for future reference */
    expr->argexpr = strdup(arg);
  }

  err = parse_expr2 (parse, tokens, ntokens, lvalue, expr);
  if (err) return nerr_pass(err);
  return STATUS_OK;
}

static NEOERR *literal_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;

  /* ne_warn ("literal: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  node->arg1.op_type = CS_TYPE_STRING;
  node->arg1.s = arg;
  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *literal_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;

  if (node->arg1.s != NULL)
    err = parse->output_cb (parse->output_ctx, node->arg1.s);
  *next = node->next;
  return nerr_pass(err);
}

static NEOERR *name_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  char *a, *s;
  char tmp[256];

  /* ne_warn ("name: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  a = neos_strip(arg);
  s = strpbrk(a, "#\" <>");
  if (s != NULL)
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c",
	find_context(parse, -1, tmp, sizeof(tmp)),
	a, s[0]);
  }

  node->arg1.op_type = CS_TYPE_VAR;
  node->arg1.s = a;
  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *escape_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  char *a = NULL;
  char tmp[256];
  CS_ESCAPE_MODES *esc_cursor;
  CSTREE *node;

  /* ne_warn ("escape: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  /* Since this throws an error always if there's a problem
   * this flag seems pointless, but following convention,
   * here it is. */
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++; /* ignore colon, space, etc */

  /* Parse the arg - we're expecting a string */
  err = parse_expr (parse, arg, 0, &(node->arg1));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }
  if (node->arg1.op_type != CS_TYPE_STRING)
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE, "%s Invalid argument for escape: %s",
      find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }

  a = neos_strip(node->arg1.s); /* Strip spaces for testing */

  /* Ensure the mode specified is allowed */
  for (esc_cursor = &EscapeModes[0];
       esc_cursor->mode != NULL;
       esc_cursor++)
    if (!strncasecmp(a, esc_cursor->mode, strlen(esc_cursor->mode)))
    {
      if (err != STATUS_OK) return nerr_pass(err);
      parse->escaping.next_stack = esc_cursor->context;
      break;
    }
  /* Didn't find an acceptable value we were looking for */
  if (esc_cursor->mode == NULL)
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE, "%s Invalid argument for escape: %s",
      find_context(parse, -1, tmp, sizeof(tmp)), a);
  }

  *(parse->next) = node;
  parse->next = &(node->case_0);
  parse->current = node;
  return STATUS_OK;
}

static NEOERR *name_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  HDF *obj;
  char *v;

  if (node->arg1.op_type == CS_TYPE_VAR && node->arg1.s != NULL)
  {
    obj = var_lookup_obj (parse, node->arg1.s);
    if (obj != NULL)
    {
      v = hdf_obj_name(obj);
      err = parse->output_cb (parse->output_ctx, v);
    }
  }
  *next = node->next;
  return nerr_pass(err);
}

static NEOERR *var_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  STACK_ENTRY *entry;

  err = uListGet (parse->stack, -1, (void *)&entry);
  if (err != STATUS_OK) return nerr_pass(err);

  /* ne_warn ("var: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;

  /* Default escape the variable based on
   * current stack's escape context except for
   * uvar:
   */
  if (!strcmp(Commands[cmd].cmd, "uvar"))
    node->escape = NEOS_ESCAPE_NONE;
  else
    node->escape = entry->escape;


  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  err = parse_expr (parse, arg, 0, &(node->arg1));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *lvar_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;

  /* ne_warn ("lvar: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  err = parse_expr (parse, arg, 0, &(node->arg1));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *linclude_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;

  /* ne_warn ("linclude: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  err = parse_expr (parse, arg, 0, &(node->arg1));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *alt_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;

  /* ne_warn ("var: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  err = parse_expr (parse, arg, 0, &(node->arg1));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->case_0);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *evar_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  char *a, *s;
  const char *save_context;
  int save_infile;
  char tmp[256];

  /* ne_warn ("evar: %s", arg); */
  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  a = neos_strip(arg);
  s = strpbrk(a, "#\" <>");
  if (s != NULL)
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE, "%s Invalid character in var name %s: %c",
	find_context(parse, -1, tmp, sizeof(tmp)),
	a, s[0]);
  }

  err = hdf_get_copy (parse->hdf, a, &s, NULL);
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass (err);
  }
  if (node->flags & CSF_REQUIRED && s == NULL)
  {
    dealloc_node(&node);
    return nerr_raise (NERR_NOT_FOUND, "%s Unable to evar empty variable %s",
	find_context(parse, -1, tmp, sizeof(tmp)), a);
  }

  node->arg1.op_type = CS_TYPE_VAR;
  node->arg1.s = a;
  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  save_context = parse->context;
  save_infile = parse->in_file;
  parse->context = a;
  parse->in_file = 0;
  if (s) err = cs_parse_string (parse, s, strlen(s));
  parse->context = save_context;
  parse->in_file = save_infile;

  return nerr_pass (err);
}

static NEOERR *if_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;

  /* ne_warn ("if: %s", arg); */
  err = alloc_node (&node, parse);
  if (err != STATUS_OK) return nerr_pass(err);
  node->cmd = cmd;
  arg++;

  err = parse_expr (parse, arg, 0, &(node->arg1));
  if (err != STATUS_OK)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->case_0);
  parse->current = node;

  return STATUS_OK;
}

char *arg_eval (CSPARSE *parse, CSARG *arg)
{
  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 %s in arg_eval", expand_token_type(arg->op_type, 1));
      return NULL;
  }
}

/* This coerces everything to numbers */
long int arg_eval_num (CSPARSE *parse, CSARG *arg)
{
  long int v = 0;

  switch ((arg->op_type & CS_TYPES))
  {
    case CS_TYPE_STRING:
      v = strtol(arg->s, NULL, 0);
      break;
    case CS_TYPE_NUM:
      v = arg->n;
      break;

    case CS_TYPE_VAR:
    case CS_TYPE_VAR_NUM:
      v = var_int_lookup (parse, arg->s);
      break;
    default:
      ne_warn ("Unsupported type %s in arg_eval_num", expand_token_type(arg->op_type, 1));
      v = 0;
      break;
  }
  return v;
}

/* This is different from arg_eval_num because we don't force strings to
 * numbers, a string is either a number (if it is all numeric) or we're
 * testing existance.  At least, that's what perl does and what dave
 * wants */
long int arg_eval_bool (CSPARSE *parse, CSARG *arg)
{
  long int v = 0;
  char *s, *r;

  switch ((arg->op_type & CS_TYPES))
  {
    case CS_TYPE_STRING:
    case CS_TYPE_VAR:
      if (arg->op_type == CS_TYPE_VAR)
	s = var_lookup(parse, arg->s);
      else
	s = arg->s;
      if (!s || *s == '\0') return 0; /* non existance or empty is false(0) */
      v = strtol(s, &r, 0);
      if (*r == '\0') /* entire string converted, treat as number */
	return v;
      /* if the entire string didn't convert, then its non-numeric and
       * exists, so its true (1) */
      return 1;
    case CS_TYPE_NUM:
      return arg->n;
    case CS_TYPE_VAR_NUM: /* this implies forced numeric evaluation */
      return var_int_lookup (parse, arg->s);
      break;
    default:
      ne_warn ("Unsupported type %s in arg_eval_bool", expand_token_type(arg->op_type, 1));
      v = 0;
      break;
  }
  return v;
}

char *arg_eval_str_alloc (CSPARSE *parse, CSARG *arg)
{
  char *s = NULL;
  char buf[256];
  long int n_val;

  switch ((arg->op_type & CS_TYPES))
  {
    case CS_TYPE_STRING:
      s = arg->s;
      break;
    case CS_TYPE_VAR:
      s = var_lookup (parse, arg->s);
      break;
    case CS_TYPE_NUM:
    case CS_TYPE_VAR_NUM:
      s = buf;
      n_val = arg_eval_num (parse, arg);
      snprintf (buf, sizeof(buf), "%ld", n_val);
      break;
    default:
      ne_warn ("Unsupported type %s in arg_eval_str_alloc",
	  expand_token_type(arg->op_type, 1));
      s = NULL;
      break;
  }
  if (s) return strdup(s);
  return NULL;
}

#if DEBUG_EXPR_EVAL
static void expand_arg (CSPARSE *parse, int depth, char *where, CSARG *arg)
{
  int x;

  for (x=0; x<depth; x++)
    fputc(' ', stderr);

  fprintf(stderr, "%s op: %s alloc: %d value: ", where, 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)
    fprintf(stderr, "%ld\n", arg->n);
  else if (arg->op_type & CS_TYPE_STRING)
    fprintf(stderr, "'%s'\n", arg->s);
  else if (arg->op_type & CS_TYPE_VAR)
    fprintf(stderr, "%s = %s\n", arg->s, var_lookup(parse, arg->s));
  else if (arg->op_type & CS_TYPE_VAR_NUM)
    fprintf(stderr, "%s = %ld\n", arg->s, var_int_lookup(parse, arg->s));
  else
    fprintf(stderr, "\n");
}
#endif

static NEOERR *eval_expr_string(CSPARSE *parse, CSARG *arg1, CSARG *arg2, CSTOKEN_TYPE op, CSARG *result)
{
  char *s1, *s2;
  int out;

  result->op_type = CS_TYPE_NUM;
  s1 = arg_eval (parse, arg1);
  s2 = arg_eval (parse, arg2);

  if ((s1 == NULL) || (s2 == NULL))
  {
    switch (op)
    {
      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(op, 1));
	break;
    }
  }
  else
  {
    out = strcmp (s1, s2);
    switch (op)
    {
      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_string", expand_token_type(op, 1));
	break;
    }
  }
  return STATUS_OK;
}

static NEOERR *eval_expr_num(CSPARSE *parse, CSARG *arg1, CSARG *arg2, CSTOKEN_TYPE op, CSARG *result)
{
  long int n1, n2;

  result->op_type = CS_TYPE_NUM;
  n1 = arg_eval_num (parse, arg1);
  n2 = arg_eval_num (parse, arg2);

  switch (op)
  {
    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_ADD:
      result->n = (n1 + n2);
      break;
    case CS_OP_SUB:
      result->n = (n1 - n2);
      break;
    case CS_OP_MULT:
      result->n = (n1 * n2);
      break;
    case CS_OP_DIV:
      if (n2 == 0) result->n = UINT_MAX;
      else result->n = (n1 / n2);
      break;
    case CS_OP_MOD:
      if (n2 == 0) result->n = 0;
      else result->n = (n1 % n2);
      break;
    default:
      ne_warn ("Unsupported op %s in eval_expr_num", expand_token_type(op, 1));
      break;
  }
  return STATUS_OK;
}

static NEOERR *eval_expr_bool(CSPARSE *parse, CSARG *arg1, CSARG *arg2, CSTOKEN_TYPE op, CSARG *result)
{
  long int n1, n2;

  result->op_type = CS_TYPE_NUM;
  n1 = arg_eval_bool (parse, arg1);
  n2 = arg_eval_bool (parse, arg2);

  switch (op)
  {
    case CS_OP_AND:
      result->n = (n1 && n2) ? 1 : 0;
      break;
    case CS_OP_OR:
      result->n = (n1 || n2) ? 1 : 0;
      break;
    default:
      ne_warn ("Unsupported op %s in eval_expr_bool", expand_token_type(op, 1));
      break;
  }
  return STATUS_OK;
}

#if DEBUG_EXPR_EVAL
static int _depth = 0;
#endif

static NEOERR *eval_expr (CSPARSE *parse, CSARG *expr, CSARG *result)
{
  NEOERR *err;

  if (expr == NULL)
    return nerr_raise (NERR_ASSERT, "expr is NULL");
  if (result == NULL)
    return nerr_raise (NERR_ASSERT, "result is NULL");

#if DEBUG_EXPR_EVAL
  _depth++;
  expand_arg(parse, _depth, "expr", expr);
#endif

  memset(result, 0, sizeof(CSARG));
  if (expr->op_type & CS_TYPES)
  {
    *result = *expr;
    /* we transfer ownership of the string here.. ugh */
    if (expr->alloc) expr->alloc = 0;
#if DEBUG_EXPR_EVAL
    expand_arg(parse, _depth, "result", result);
    _depth--;
#endif
    return STATUS_OK;
  }

  if (expr->op_type & CS_OP_LPAREN)
  {
    /* lparen is a no-op, just skip */
    return nerr_pass(eval_expr(parse, expr->expr1, result));
  }
  if (expr->op_type & CS_TYPE_FUNCTION)
  {
    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 : "");

    /* The function evaluates all the arguments, so don't pre-evaluate
     * argument1 */
    err = expr->function->function(parse, expr->function, expr->expr1, result);
    if (err) return nerr_pass(err);
    /* Indicate whether or not an explicit escape call was made by
     * setting the mode (usually NONE or FUNCTION). This is ORed to
     * ensure that escaping calls within other functions do not get
     * double-escaped. E.g. slice(html_escape(foo), 10, 20) */
    parse->escaping.current |= expr->function->escape;
  }
  else
  {
    CSARG arg1, arg2;
    arg1.alloc = 0;
    arg2.alloc = 0;

    err = eval_expr (parse, expr->expr1, &arg1);
    if (err) return nerr_pass(err);
#if DEBUG_EXPR_EVAL
    expand_arg(parse, _depth, "arg1", &arg1);
#endif
    if (expr->op_type & CS_OPS_UNARY)
    {
      result->op_type = CS_TYPE_NUM;
      switch (expr->op_type) {
        case CS_OP_NOT:
          result->n = arg_eval_bool(parse, &arg1) ? 0 : 1;
          break;
        case CS_OP_EXISTS:
          if (arg1.op_type & (CS_TYPE_VAR | CS_TYPE_VAR_NUM))
          {
            if (arg_eval(parse, &arg1) == NULL)
              result->n = 0;
            else
              result->n = 1;
          }
          else
          {
            /* All numbers/strings exist */
            result->n = 1;
          }
          break;
        case CS_OP_NUM:
          result->n = arg_eval_num (parse, &arg1);
          break;
        case CS_OP_LPAREN:
          return nerr_raise(NERR_ASSERT, "LPAREN should be handled above");
        default:
          result->n = 0;
          ne_warn ("Unsupported op %s in eval_expr", expand_token_type(expr->op_type, 1));
          break;
      }
    }
    else if (expr->op_type == CS_OP_COMMA)
    {
      /* The comma operator, like in C, we return the value of the right
       * most argument, in this case that's expr1, but we still need to
       * evaluate the other stuff */
      if (expr->next)
      {
        err = eval_expr (parse, expr->next, &arg2);
#if DEBUG_EXPR_EVAL
        expand_arg(parse, _depth, "arg2", &arg2);
#endif
        if (err) return nerr_pass(err);
        if (arg2.alloc) free(arg2.s);
      }
      *result = arg1;
      /* we transfer ownership of the string here.. ugh */
      if (arg1.alloc) arg1.alloc = 0;
#if DEBUG_EXPR_EVAL
      expand_arg(parse, _depth, "result", result);
      _depth--;
#endif
      return STATUS_OK;
    }
    else
    {
      err = eval_expr (parse, expr->expr2, &arg2);
#if DEBUG_EXPR_EVAL
      expand_arg(parse, _depth, "arg2", &arg2);
#endif
      if (err) return nerr_pass(err);

      if (expr->op_type == CS_OP_LBRACKET)
      {
        /* the bracket op is essentially hdf array lookups, which just
         * means appending the value of arg2, .0 */
        result->op_type = CS_TYPE_VAR;
        result->alloc = 1;
        if (arg2.op_type & (CS_TYPE_VAR_NUM | CS_TYPE_NUM))
        {
          long int n2 = arg_eval_num (parse, &arg2);
          result->s = sprintf_alloc("%s.%ld", arg1.s, n2);
          if (result->s == NULL)
            return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %ld", arg1.s, n2);
        }
        else
        {
          char *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 (expr->op_type == CS_OP_DOT)
      {
        /* the dot op is essentially extending the hdf name, which just
         * means appending the string .0 */
        result->op_type = CS_TYPE_VAR;
        result->alloc = 1;
        if (arg2.op_type & CS_TYPES_VAR)
        {
          result->s = sprintf_alloc("%s.%s", arg1.s, arg2.s);
          if (result->s == NULL)
            return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %s", arg1.s, arg2.s);
        }
        else
        {
          if (arg2.op_type & CS_TYPE_NUM)
          {
            long int n2 = arg_eval_num (parse, &arg2);
            result->s = sprintf_alloc("%s.%ld", arg1.s, n2);
            if (result->s == NULL)
              return nerr_raise (NERR_NOMEM, "Unable to allocate memory to concatenate varnames in expression: %s + %ld", arg1.s, n2);
          }
          else
          {
            char *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 (expr->op_type & (CS_OP_AND | CS_OP_OR))
      {
        /* eval as bool */
        err = eval_expr_bool (parse, &arg1, &arg2, expr->op_type, result);
      }
      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 | CS_OP_GT | CS_OP_GTE | CS_OP_LT | CS_OP_LTE)))
      {
        /* eval as num */
        err = eval_expr_num(parse, &arg1, &arg2, expr->op_type, result);
      }
      else /* eval as string */
      {
        err = eval_expr_string(parse, &arg1, &arg2, expr->op_type, result);
      }
    }
    if (arg1.alloc) free(arg1.s);
    if (arg2.alloc) free(arg2.s);
  }

#if DEBUG_EXPR_EVAL
  expand_arg(parse, _depth, "result", result);
  _depth--;
#endif
  return STATUS_OK;
}

static NEOERR *var_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CSARG val;

  parse->escaping.current = NEOS_ESCAPE_NONE;
  err = eval_expr(parse, &(node->arg1), &val);
  if (err) return nerr_pass(err);
  if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
  {
    char buf[256];
    long int n_val;

    n_val = arg_eval_num (parse, &val);
    snprintf (buf, sizeof(buf), "%ld", n_val);
    err = parse->output_cb (parse->output_ctx, buf);
  }
  else
  {
    char *s = arg_eval (parse, &val);
    /* Determine if the node has been escaped by an explicit function. If not
     * call to escape. node->escape should contain the default escaping from
     * Config.VarEscapeMode and parse->escaping.current will have a non-zero
     * value if an explicit escape call was made sooooo.
     */
    if (s && parse->escaping.current == NEOS_ESCAPE_NONE) /* no explicit escape */
    {
      char *escaped = NULL;
      /* Use default escape if escape is UNDEF */
      if (node->escape == NEOS_ESCAPE_UNDEF)
        err = neos_var_escape(parse->escaping.when_undef, s, &escaped);
      else
        err = neos_var_escape(node->escape, s, &escaped);

      if (escaped)
      {
        err = parse->output_cb (parse->output_ctx, escaped);
        free(escaped);
      }
    }
    else if (s)
    { /* already explicitly escaped */
      err = parse->output_cb (parse->output_ctx, s);
    }
    /* Do we set it to blank if s == NULL? */
  }
  if (val.alloc) free(val.s);

  *next = node->next;
  return nerr_pass(err);
}

static NEOERR *lvar_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CSARG val;

  err = eval_expr(parse, &(node->arg1), &val);
  if (err) return nerr_pass(err);
  if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
  {
    char buf[256];
    long int n_val;

    n_val = arg_eval_num (parse, &val);
    snprintf (buf, sizeof(buf), "%ld", n_val);
    err = parse->output_cb (parse->output_ctx, buf);
  }
  else
  {
    char *s = arg_eval (parse, &val);

    if (s)
    {
      CSPARSE *cs = NULL;

      /* Ok, we need our own copy of the string to pass to
       * cs_parse_string... */
      if (val.alloc && (val.op_type & CS_TYPE_STRING)) {
	val.alloc = 0;
      }
      else
      {
	s = strdup(s);
	if (s == NULL)
	{
	  return nerr_raise(NERR_NOMEM, "Unable to allocate memory for lvar_eval");
	}
      }

      do {
	err = cs_init_internal(&cs, parse->hdf, parse);
	if (err) break;
	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_destroy(&cs);
    }
  }
  if (val.alloc) free(val.s);

  *next = node->next;
  return nerr_pass(err);
}

static NEOERR *linclude_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CSARG val;

  err = eval_expr(parse, &(node->arg1), &val);
  if (err) return nerr_pass(err);
  if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
  {
    char buf[256];
    long int n_val;

    n_val = arg_eval_num (parse, &val);
    snprintf (buf, sizeof(buf), "%ld", n_val);
    err = parse->output_cb (parse->output_ctx, buf);
  }
  else
  {
    char *s = arg_eval (parse, &val);

    if (s)
    {
      CSPARSE *cs = NULL;
      do {
	err = cs_init_internal(&cs, parse->hdf, parse);
	if (err) break;
	err = cs_parse_file(cs, s);
	if (!(node->flags & CSF_REQUIRED))
	{
	  nerr_handle(&err, NERR_NOT_FOUND);
	}
	if (err) break;
	err = cs_render(cs, parse->output_ctx, parse->output_cb);
	if (err) break;
      } while (0);
      cs_destroy(&cs);
    }
  }
  if (val.alloc) free(val.s);

  *next = node->next;
  return nerr_pass(err);
}

/* if the expr evaluates to true, display it, else render the alternate */
static NEOERR *alt_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CSARG val;
  int eval_true = 1;

  err = eval_expr(parse, &(node->arg1), &val);
  if (err) return nerr_pass(err);
  eval_true = arg_eval_bool(parse, &val);
  if (eval_true)
  {
    if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
    {
      char buf[256];
      long int n_val;

      n_val = arg_eval_num (parse, &val);
      snprintf (buf, sizeof(buf), "%ld", n_val);
      err = parse->output_cb (parse->output_ctx, buf);
    }
    else
    {
      char *s = arg_eval (parse, &val);
      /* Do we set it to blank if s == NULL? */
      if (s)
      {
	err = parse->output_cb (parse->output_ctx, s);
      }
    }
  }
  if (val.alloc) free(val.s);

  if (eval_true == 0)
  {
    err = render_node (parse, node->case_0);
  }

  *next = node->next;
  return nerr_pass(err);
}

/* just calls through to the child nodes */
static NEOERR *escape_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  /* TODO(wad): Should I set a eval-time value here? */
  err = render_node (parse, node->case_0);
  *next = node->next;
  return nerr_pass(err);
}


static NEOERR *if_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  int eval_true = 0;
  CSARG val;

  err = eval_expr(parse, &(node->arg1), &val);
  if (err) return nerr_pass (err);
  eval_true = arg_eval_bool(parse, &val);
  if (val.alloc) free(val.s);

  if (eval_true)
  {
    err = render_node (parse, node->case_0);
  }
  else if (node->case_1 != NULL)
  {
    err = render_node (parse, node->case_1);
  }
  *next = node->next;
  return nerr_pass (err);
}

static NEOERR *else_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  STACK_ENTRY *entry;

  /* ne_warn ("else"); */
  err = uListGet (parse->stack, -1, (void *)&entry);
  if (err != STATUS_OK) return nerr_pass(err);

  parse->next = &(entry->tree->case_1);
  parse->current = entry->tree;
  return STATUS_OK;
}

static NEOERR *elif_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  STACK_ENTRY *entry;

  /* ne_warn ("elif: %s", arg); */
  err = uListGet (parse->stack, -1, (void *)&entry);
  if (err != STATUS_OK) return nerr_pass(err);

  if (entry->next_tree == NULL)
    entry->next_tree = entry->tree;

  parse->next = &(entry->tree->case_1);

  err = if_parse(parse, cmd, arg);
  entry->tree = parse->current;
  return nerr_pass(err);
}

static NEOERR *endif_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  STACK_ENTRY *entry;

  /* ne_warn ("endif"); */
  err = uListGet (parse->stack, -1, (void *)&entry);
  if (err != STATUS_OK) return nerr_pass(err);

  if (entry->next_tree)
    parse->next = &(entry->next_tree->next);
  else
    parse->next = &(entry->tree->next);
  parse->current = entry->tree;
  return STATUS_OK;
}

static NEOERR *each_with_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  char *lvar;
  char *p;
  char tmp[256];

  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;

  p = lvar = neos_strip(arg);
  while (*p && !isspace(*p) && *p != '=') p++;
  if (*p == '\0')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Improperly formatted %s directive: %s",
	find_context(parse, -1, tmp, sizeof(tmp)), Commands[cmd].cmd, arg);
  }
  if (*p != '=')
  {
    *p++ = '\0';
    while (*p && *p != '=') p++;
    if (*p == '\0')
    {
      dealloc_node(&node);
      return nerr_raise (NERR_PARSE,
	  "%s Improperly formatted %s directive: %s",
	  find_context(parse, -1, tmp, sizeof(tmp)), Commands[cmd].cmd, arg);
    }
    p++;
  }
  else
  {
    *p++ = '\0';
  }
  while (*p && isspace(*p)) p++;
  if (*p == '\0')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Improperly formatted %s directive: %s",
	find_context(parse, -1, tmp, sizeof(tmp)), Commands[cmd].cmd, arg);
  }
  node->arg1.op_type = CS_TYPE_VAR;
  node->arg1.s = lvar;

  err = parse_expr(parse, p, 0, &(node->arg2));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }
  /* ne_warn ("each %s %s", lvar, p); */

  *(parse->next) = node;
  parse->next = &(node->case_0);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *each_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CS_LOCAL_MAP each_map;
  CSARG val;
  HDF *var, *child;

  memset(&each_map, 0, sizeof(each_map));

  err = eval_expr(parse, &(node->arg2), &val);
  if (err) return nerr_pass(err);

  if (val.op_type == CS_TYPE_VAR)
  {
    var = var_lookup_obj (parse, val.s);

    if (var != NULL)
    {
      /* Init and install local map */
      each_map.type = CS_TYPE_VAR;
      each_map.name = node->arg1.s;
      each_map.next = parse->locals;
      each_map.first = 1;
      each_map.last = 0;
      parse->locals = &each_map;

      do
      {
	child = hdf_obj_child (var);
	while (child != NULL)
	{
          /* We don't explicitly set each_map.last here since checking
           * requires a function call, so we move the check to _builtin_last
           * so it only makes the call if last() is being used */
	  each_map.h = child;
	  err = render_node (parse, node->case_0);
          if (each_map.map_alloc) {
            free(each_map.s);
            each_map.s = NULL;
          }
          if (each_map.first) each_map.first = 0;
	  if (err != STATUS_OK) break;
	  child = hdf_obj_next (child);
	}

      } while (0);

      /* Remove local map */
      parse->locals = each_map.next;
    }
  } /* else WARNING */
  if (val.alloc) free(val.s);

  *next = node->next;
  return nerr_pass (err);
}

static NEOERR *with_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CS_LOCAL_MAP with_map;
  CSARG val;
  HDF *var;

  memset(&with_map, 0, sizeof(with_map));

  err = eval_expr(parse, &(node->arg2), &val);
  if (err) return nerr_pass(err);

  if (val.op_type == CS_TYPE_VAR)
  {
    var = var_lookup_obj (parse, val.s);

    if (var != NULL)
    {
      /* Init and install local map */
      with_map.type = CS_TYPE_VAR;
      with_map.name = node->arg1.s;
      with_map.next = parse->locals;
      with_map.h = var;
      parse->locals = &with_map;
      err = render_node (parse, node->case_0);
      /* Remove local map */
      if (with_map.map_alloc) free(with_map.s);
      parse->locals = with_map.next;
    }
  }
  else
  {
    /* else WARNING */
    ne_warn("Invalid op_type for with: %s", expand_token_type(val.op_type, 1));
  }
  if (val.alloc) free(val.s);

  *next = node->next;
  return nerr_pass (err);
}
static NEOERR *end_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  STACK_ENTRY *entry;

  err = uListGet (parse->stack, -1, (void *)&entry);
  if (err != STATUS_OK) return nerr_pass(err);

  parse->next = &(entry->tree->next);
  parse->current = entry->tree;
  return STATUS_OK;
}

static NEOERR *include_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  char *s;
  int flags = 0;
  CSARG arg1, val;

  memset(&arg1, 0, sizeof(CSARG));
  if (arg[0] == '!')
    flags |= CSF_REQUIRED;
  arg++;
  /* Validate arg is a var (regex /^[#" ]$/) */
  err = parse_expr (parse, arg, 0, &arg1);
  if (err) return nerr_pass(err);
  /* ne_warn ("include: %s", a); */

  err = eval_expr(parse, &arg1, &val);
  if (err) return nerr_pass(err);

  s = arg_eval (parse, &val);
  if (s == NULL && !(flags & CSF_REQUIRED))
    return STATUS_OK;
  err = cs_parse_file(parse, s);
  if (!(flags & CSF_REQUIRED))
  {
    nerr_handle(&err, NERR_NOT_FOUND);
  }
  if (val.alloc) free(val.s);

  return nerr_pass (err);
}

static NEOERR *def_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  CS_MACRO *macro;
  CSARG *carg, *larg = NULL;
  char *a = NULL, *p = NULL, *s;
  char tmp[256];
  char name[256];
  int x = 0;
  BOOL last = FALSE;

  /* Since def doesn't get a new stack entry until after this is run,
   * setup a dumb var on the parse object to hold the future setting.
   */
  parse->escaping.next_stack = NEOS_ESCAPE_UNDEF;

  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  arg++;
  s = arg;
  while (*s && *s != ' ' && *s != '#' && *s != '(')
  {
    name[x++] = *s;
    s++;
  }
  name[x] = '\0';
  while (*s && isspace(*s)) s++;
  if (*s == '\0' || *s != '(')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Missing left paren in macro def %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }
  s++;
  /* Check to see if this is a redefinition */
  macro = parse->macros;
  while (macro != NULL)
  {
    if (!strcmp(macro->name, name))
    {
      dealloc_node(&node);
      return nerr_raise (NERR_PARSE,
	  "%s Duplicate macro def for %s",
	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
    }
    macro = macro->next;
  }

  macro = (CS_MACRO *) calloc (1, sizeof (CS_MACRO));
  if (macro) macro->name = strdup(name);
  if (macro == NULL || macro->name == NULL)
  {
    dealloc_node(&node);
    dealloc_macro(&macro);
    return nerr_raise (NERR_NOMEM,
	"%s Unable to allocate memory for CS_MACRO in def %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }

  while (*s)
  {
    while (*s && isspace(*s)) 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;
    }
    if (*a == ')') last = TRUE;
    *a = '\0';
    /* cut out ending whitespace */
    p = strpbrk(s, " \t\r\n");
    if (p != NULL) *p = '\0';
    p = strpbrk(s, "\"?<>=!#-+|&,)*/%[]( \t\r\n");
    if (p != NULL)
    {
      err = nerr_raise (NERR_PARSE,
	  "%s Invalid character in def %s argument: %c",
	  find_context(parse, -1, tmp, sizeof(tmp)), arg, *p);
      break;
    }
    /* No argument case */
    if (*s == '\0' && macro->n_args == 0) break;
    if (*s == '\0')
    {
      err = nerr_raise (NERR_PARSE,
	  "%s Missing argument name or extra comma in def %s",
	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
      break;
    }
    carg = (CSARG *) calloc (1, sizeof(CSARG));
    if (carg == NULL)
    {
      err = nerr_raise (NERR_NOMEM,
	  "%s Unable to allocate memory for CSARG in def %s",
	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
      break;
    }
    if (larg == NULL)
    {
      macro->args = carg;
      larg = carg;
    }
    else
    {
      larg->next = carg;
      larg = carg;
    }
    macro->n_args++;
    carg->s = s;
    if (last == TRUE) break;
    s = a+1;
  }
  if (err)
  {
    dealloc_node(&node);
    dealloc_macro(&macro);
    return nerr_pass(err);
  }

  macro->tree = node;
  if (parse->macros)
  {
    macro->next = parse->macros;
  }
  parse->macros = macro;

  *(parse->next) = node;
  parse->next = &(node->case_0);
  parse->current = node;

  return STATUS_OK;
}

static int rearrange_for_call(CSARG **args)
{
  CSARG *larg = NULL;
  CSARG *carg = *args;
  CSARG *vargs = NULL;
  int nargs = 0;

  /* multiple argument case, we have to walk the args and reverse
   * them. Also handles single arg case since its the same as the
   * last arg */
  while (carg)
  {
    nargs++;
    if (carg->op_type != CS_OP_COMMA)
    {
      /* last argument */
      if (vargs)
	carg->next = vargs;
      vargs = carg;
      break;
    }
    if (vargs)
      carg->expr1->next = vargs;
    vargs = carg->expr1;
    larg = carg;
    carg = carg->next;
    /* dealloc comma, but not its descendents */
    larg->next = NULL;
    larg->expr1 = NULL;
    dealloc_arg(&larg);
  }
  *args = vargs;

  return nargs;
}

static NEOERR *call_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  CS_MACRO *macro;
  CSARG *carg;
  char *s, *a = NULL;
  char tmp[256];
  char name[256];
  int x = 0;
  int nargs = 0;
  STACK_ENTRY *entry;

  err = uListGet (parse->stack, -1, (void *)&entry);
  if (err != STATUS_OK) return nerr_pass(err);

  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  node->escape = entry->escape;
  arg++;
  s = arg;
  while (x < sizeof(name) && *s && *s != ' ' && *s != '#' && *s != '(')
  {
    name[x++] = *s;
    s++;
  }
  name[x] = '\0';
  while (*s && isspace(*s)) s++;
  if (*s == '\0' || *s != '(')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Missing left paren in call %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }
  s++;
  /* Check to see if this macro exists */
  macro = parse->macros;
  while (macro != NULL)
  {
    if (!strcmp(macro->name, name)) break;
    macro = macro->next;
  }
  if (macro == NULL)
  {
    dealloc_node(&node);
    err = nerr_raise (NERR_PARSE, "%s Undefined macro called: %s",
          find_context(parse, -1, tmp, sizeof(tmp)), arg);
    if (parse->audit_mode) {
      /* Ignore macros that cannot be found */
      return _store_error(parse, err);
    }
    else {
      return err;
    }
  }
  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 call %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }
  *a = '\0';

  while (*s && isspace(*s)) s++;
  /* No arguments case */
  if (*s == '\0')
  {
    nargs = 0;
  }
  else
  {
    /* Parse arguments case */
    do
    {
      carg = (CSARG *) calloc (1, sizeof(CSARG));
      if (carg == NULL)
      {
	err = nerr_raise (NERR_NOMEM,
	    "%s Unable to allocate memory for CSARG in call %s",
	    find_context(parse, -1, tmp, sizeof(tmp)), arg);
	break;
      }
      err = parse_expr (parse, s, 0, carg);
      if (err) break;
      nargs = rearrange_for_call(&carg);
      node->vargs = carg;
    } while (0);
  }
  if (!err && nargs != macro->n_args)
  {
    err = nerr_raise (NERR_PARSE,
	"%s Incorrect number of arguments, expected %d, got %d in call to macro %s: %s",
	find_context(parse, -1, tmp, sizeof(tmp)), macro->n_args, nargs,
	macro->name, arg);
  }
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *call_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CS_LOCAL_MAP *call_map, *map;
  CS_MACRO *macro;
  CSARG *carg, *darg;
  HDF *var;
  int x;

  /* Reset the value of when_undef for the coming call evaluation.
   * This is only used here so it there's no need to reset its value after
   * the call. If this call is nested (escape == NEOS_ESCAPE_UNDEF), then
   * leave the when_undef variable alone. The parent call_eval should have
   * already defined it.
   */
  if (node->escape != NEOS_ESCAPE_UNDEF)
    parse->escaping.when_undef = node->escape;

  macro = node->arg1.macro;
  if (macro->n_args)
  {
    call_map = (CS_LOCAL_MAP *) calloc (macro->n_args, sizeof(CS_LOCAL_MAP));
    if (call_map == NULL)
      return nerr_raise (NERR_NOMEM,
                "Unable to allocate memory for call_map in call_eval of %s",
                         macro->name);
  }
  else
  {
    call_map = NULL;
  }

  darg = macro->args;
  carg = node->vargs;

  for (x = 0; x < macro->n_args; x++)
  {
    CSARG val;
    map = &call_map[x];
    if (x) call_map[x-1].next = map;

    map->name = darg->s;
    err = eval_expr(parse, carg, &val);
    if (err) break;
    if (val.op_type & CS_TYPE_STRING)
    {
      map->s = val.s;
      map->type = val.op_type;
      map->map_alloc = val.alloc;
      val.alloc = 0;
    }
    else if (val.op_type & CS_TYPE_NUM)
    {
      map->n = val.n;
      map->type = CS_TYPE_NUM;
    }
    else if (val.op_type & (CS_TYPE_VAR | CS_TYPE_VAR_NUM))
    {
      CS_LOCAL_MAP *lmap;
      char *c;
      lmap = lookup_map (parse, val.s, &c);
      if (lmap != NULL && (lmap->type != CS_TYPE_VAR && lmap->type != CS_TYPE_VAR_NUM))
      {
	/* if we're referencing a local var which maps to a string or
	 * number... then copy  */
	if (lmap->type == CS_TYPE_NUM)
	{
	  map->n = lmap->n;
	  map->type = lmap->type;
	}
	else
	{
	  map->s = lmap->s;
	  map->type = lmap->type;
	}
      }
      else
      {
	var = var_lookup_obj (parse, val.s);
	map->h = var;
        map->type = CS_TYPE_VAR;
        /* Bring across the name we're mapping to, in case h doesn't exist and
         * we need to set it. */
        map->s = val.s;
        map->map_alloc = val.alloc;
        val.alloc = 0;
      }
    }
    else
    {
      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;

    darg = darg->next;
    carg = carg->next;
  }

  if (err == STATUS_OK)
  {
    map = parse->locals;
    if (macro->n_args) parse->locals = call_map;
    err = render_node (parse, macro->tree->case_0);
    parse->locals = map;
  }
  for (x = 0; x < macro->n_args; x++)
  {
    if (call_map[x].map_alloc) free(call_map[x].s);
  }
  if (call_map) free (call_map);

  *next = node->next;
  return nerr_pass(err);
}

static NEOERR *set_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  char *s;
  char tmp[256];

  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  arg++;
  s = arg;
  while (*s && *s != '=') s++;
  if (*s == '\0')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Missing equals in set %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }
  *s = '\0';
  s++;
  err = parse_expr(parse, arg, 1, &(node->arg1));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  err = parse_expr(parse, s, 0, &(node->arg2));
  if (err)
  {
    dealloc_node(&node);
    return nerr_pass(err);
  }

  *(parse->next) = node;
  parse->next = &(node->next);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *set_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CSARG val;
  CSARG set;

  err = eval_expr(parse, &(node->arg1), &set);
  if (err) return nerr_pass (err);
  err = eval_expr(parse, &(node->arg2), &val);
  if (err) {
    if (set.alloc) free(set.s);
    return nerr_pass (err);
  }

  if (set.op_type != CS_TYPE_NUM)
  {
    /* this allow for a weirdness where set:"foo"="bar"
     * actually sets the hdf var foo... */
    if (val.op_type & (CS_TYPE_NUM | CS_TYPE_VAR_NUM))
    {
      char buf[256];
      long int n_val;

      n_val = arg_eval_num (parse, &val);
      snprintf (buf, sizeof(buf), "%ld", n_val);
      if (set.s)
      {
	err = var_set_value (parse, set.s, buf);
      }
      else
      {
	err = nerr_raise(NERR_ASSERT,
	    "lvalue is NULL/empty in attempt to evaluate set to '%s'", buf);
      }
    }
    else
    {
      char *s = arg_eval (parse, &val);
      /* Do we set it to blank if s == NULL? */
      if (set.s)
      {
	err = var_set_value (parse, set.s, s);
      }
      else
      {
	err = nerr_raise(NERR_ASSERT,
	    "lvalue is NULL/empty in attempt to evaluate set to '%s'",
	    (s) ? s : "");
      }
    }
  } /* else WARNING */
  if (set.alloc) free(set.s);
  if (val.alloc) free(val.s);

  *next = node->next;
  return nerr_pass (err);
}

static NEOERR *loop_parse (CSPARSE *parse, int cmd, char *arg)
{
  NEOERR *err;
  CSTREE *node;
  CSARG *carg, *larg = NULL;
  BOOL last = FALSE;
  char *lvar;
  char *p, *a;
  char tmp[256];
  int x;

  err = alloc_node (&node, parse);
  if (err) return nerr_pass(err);
  node->cmd = cmd;
  if (arg[0] == '!')
    node->flags |= CSF_REQUIRED;
  arg++;

  p = lvar = neos_strip(arg);
  while (*p && !isspace(*p) && *p != '=') p++;
  if (*p == '\0')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Improperly formatted loop directive: %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }
  if (*p != '=')
  {
    *p++ = '\0';
    while (*p && *p != '=') p++;
    if (*p == '\0')
    {
      dealloc_node(&node);
      return nerr_raise (NERR_PARSE,
	  "%s Improperly formatted loop directive: %s",
	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
    }
    p++;
  }
  else
  {
    *p++ = '\0';
  }
  while (*p && isspace(*p)) p++;
  if (*p == '\0')
  {
    dealloc_node(&node);
    return nerr_raise (NERR_PARSE,
	"%s Improperly formatted loop directive: %s",
	find_context(parse, -1, tmp, sizeof(tmp)), arg);
  }
  node->arg1.op_type = CS_TYPE_VAR;
  node->arg1.s = lvar;

  x = 0;
  while (*p)
  {
    carg = (CSARG *) calloc (1, sizeof(CSARG));
    if (carg == NULL)
    {
      err = nerr_raise (NERR_NOMEM,
	  "%s Unable to allocate memory for CSARG in loop %s",
	  find_context(parse, -1, tmp, sizeof(tmp)), arg);
      break;
    }
    if (larg == NULL)
    {
      node->vargs = carg;
      larg = carg;
    }
    else
    {
      larg->next = carg;
      larg = carg;
    }
    x++;
    a = strpbrk(p, ",");
    if (a == NULL) last = TRUE;
    else *a = '\0';
    err = parse_expr (parse, p, 0, carg);
    if (err) break;
    if (last == TRUE) break;
    p = a+1;
  }
  if (!err && ((x < 1) || (x > 3)))
  {
    err = nerr_raise (NERR_PARSE,
	"%s Incorrect number of arguments, expected 1, 2, or 3 got %d in loop: %s",
	find_context(parse, -1, tmp, sizeof(tmp)), x, arg);
  }

  /* ne_warn ("loop %s %s", lvar, p); */

  *(parse->next) = node;
  parse->next = &(node->case_0);
  parse->current = node;

  return STATUS_OK;
}

static NEOERR *loop_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  NEOERR *err = STATUS_OK;
  CS_LOCAL_MAP each_map;
  int var;
  int start = 0, end = 0, step = 1;
  int x, iter = 1;
  CSARG *carg;
  CSARG val;

  memset(&each_map, 0, sizeof(each_map));

  carg = node->vargs;
  if (carg == NULL) return nerr_raise (NERR_ASSERT, "No arguments in loop eval?");
  err = eval_expr(parse, carg, &val);
  if (err) return nerr_pass(err);
  end = arg_eval_num(parse, &val);
  if (val.alloc) free(val.s);
  if (carg->next)
  {
    start = end;
    carg = carg->next;
    err = eval_expr(parse, carg, &val);
    if (err) return nerr_pass(err);
    end = arg_eval_num(parse, &val);
    if (val.alloc) free(val.s);
    if (carg->next)
    {
      carg = carg->next;
      err = eval_expr(parse, carg, &val);
      if (err) return nerr_pass(err);
      step = arg_eval_num(parse, &val);
      if (val.alloc) free(val.s);
    }
  }
  if (((step < 0) && (start < end)) ||
      ((step > 0) && (end < start)))
  {
    iter = 0;
  }
  else if (step == 0)
  {
    iter = 0;
  }
  else
  {
    iter = abs((end - start) / step + 1);
  }

  if (iter > 0)
  {
    /* Init and install local map */
    each_map.type = CS_TYPE_NUM;
    each_map.name = node->arg1.s;
    each_map.next = parse->locals;
    each_map.first = 1;
    parse->locals = &each_map;

    var = start;
    for (x = 0, var = start; x < iter; x++, var += step)
    {
      if (x == iter - 1) each_map.last = 1;
      each_map.n = var;
      err = render_node (parse, node->case_0);
      if (each_map.map_alloc) {
        free(each_map.s);
        each_map.s = NULL;
      }
      if (each_map.first) each_map.first = 0;
      if (err != STATUS_OK) break;
    }

    /* Remove local map */
    parse->locals = each_map.next;
  }

  *next = node->next;
  return nerr_pass (err);
}

static NEOERR *skip_eval (CSPARSE *parse, CSTREE *node, CSTREE **next)
{
  *next = node->next;
  return STATUS_OK;
}
static NEOERR *render_node (CSPARSE *parse, CSTREE *node)
{
  NEOERR *err = STATUS_OK;

  while (node != NULL)
  {
    /* ne_warn ("%s %08x", Commands[node->cmd].cmd, node); */
    err = (*(Commands[node->cmd].eval_handler))(parse, node, &node);
    if (err) break;
  }
  return nerr_pass(err);
}

NEOERR *cs_render (CSPARSE *parse, void *ctx, CSOUTFUNC cb)
{
  CSTREE *node;

  if (parse->tree == NULL)
    return nerr_raise (NERR_ASSERT, "No parse tree exists");

  parse->output_ctx = ctx;
  parse->output_cb = cb;

  node = parse->tree;
  return nerr_pass (render_node(parse, node));
}

/* **** Functions ******************************************** */

NEOERR *cs_register_function(CSPARSE *parse, const char *funcname,
                                  int n_args, CSFUNCTION function)
{
  CS_FUNCTION *csf;

  /* 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->escape = NEOS_ESCAPE_NONE;
  csf->next = parse->functions;
  parse->functions = csf;

  return STATUS_OK;
}

/* This is similar to python's PyArg_ParseTuple, :
 *   s - string (allocated)
 *   i - int
 *   A - arg ptr (maybe later)
 */
NEOERR * cs_arg_parsev(CSPARSE *parse, CSARG *args, const char *fmt,
                       va_list ap)
{
  NEOERR *err = STATUS_OK;
  char **s;
  long int *i;
  CSARG val;

  while (*fmt)
  {
    memset(&val, 0, sizeof(val));
    err = eval_expr(parse, args, &val);
    if (err) return nerr_pass(err);

    switch (*fmt)
    {
      case 's':
	s = va_arg(ap, char **);
	if (s == NULL)
	{
	  err = nerr_raise(NERR_ASSERT,
	      "Invalid number of arguments in call to cs_arg_parse");
	  break;
	}
	*s = arg_eval_str_alloc(parse, &val);
	break;
      case 'i':
	i = va_arg(ap, long int *);
	if (i == NULL)
	{
	  err = nerr_raise(NERR_ASSERT,
	      "Invalid number of arguments in call to cs_arg_parse");
	  break;
	}
	*i = arg_eval_num(parse, &val);
	break;
      default:
	break;
    }
    if (err) return nerr_pass(err);
    fmt++;
    args = args->next;
    if (val.alloc) free(val.s);
  }
  if (err) return nerr_pass(err);
  return STATUS_OK;
}

NEOERR * cs_arg_parse(CSPARSE *parse, CSARG *args, const char *fmt, ...)
{
  NEOERR *err;
  va_list ap;

  va_start(ap, fmt);
  err = cs_arg_parsev(parse, args, fmt, ap);
  va_end(ap);
  return nerr_pass(err);
}

static NEOERR * _builtin_subcount(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  HDF *obj;
  int count = 0;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  /* default for non-vars is 0 children */
  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  if (val.op_type & CS_TYPE_VAR)
  {
    obj = var_lookup_obj (parse, val.s);
    if (obj != NULL)
    {
      obj = hdf_obj_child(obj);
      while (obj != NULL)
      {
	count++;
	obj = hdf_obj_next(obj);
      }
    }
    result->n = count;
  }
  if (val.alloc) free(val.s);

  return STATUS_OK;
}

static NEOERR * _builtin_str_length(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  /* non var/string objects have 0 length */
  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  if (val.op_type & (CS_TYPE_VAR | CS_TYPE_STRING))
  {
    char *s = arg_eval(parse, &val);
    if (s) result->n = strlen(s);
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}

static NEOERR * _builtin_str_crc(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args,
                                 CSARG *result)
{
  NEOERR *err;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  /* non var/string objects have 0 length */
  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  if (val.op_type & (CS_TYPE_VAR | CS_TYPE_STRING))
  {
    char *s = arg_eval(parse, &val);
    if (s) result->n = ne_crc((unsigned char *)s, strlen(s));
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}


static NEOERR * _builtin_str_find(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  char *s = NULL;
  char *substr = NULL;
  char *pstr = NULL;

  result->op_type = CS_TYPE_NUM;
  result->n = -1;

  err = cs_arg_parse(parse, args, "ss", &s, &substr);
  if (err) return nerr_pass(err);
  /* If null arguments, return -1 index */
  if (s == NULL || substr == NULL) {
    if (s) free(s);
    if (substr) free(substr);
    return STATUS_OK;
  }
  pstr = strstr(s, substr);
  if (pstr != NULL) {
    result->n = (pstr - s) / sizeof(char);
  }
  free(s);
  free(substr);
  return STATUS_OK;
}


static NEOERR * _builtin_name(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  HDF *obj;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  result->op_type = CS_TYPE_STRING;
  result->s = "";

  if (val.op_type & CS_TYPE_VAR)
  {
    obj = var_lookup_obj (parse, val.s);
    if (obj != NULL)
      result->s = hdf_obj_name(obj);
  }
  else if (val.op_type & CS_TYPE_STRING)
  {
    result->s = val.s;
    result->alloc = val.alloc;
    val.alloc = 0;
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}

/* Check to see if a local variable is the first in an each/loop sequence */
static NEOERR * _builtin_first(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args,
                               CSARG *result)
{
  NEOERR *err;
  CS_LOCAL_MAP *map;
  char *c;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  /* default is "not first" */
  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  /* Only applies to possible local vars */
  if ((val.op_type & CS_TYPE_VAR) && !strchr(val.s, '.'))
  {
    map = lookup_map (parse, val.s, &c);
    if (map && map->first)
      result->n = 1;
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}

/* Check to see if a local variable is the last in an each/loop sequence */
/* TODO: consider making this work on regular HDF vars */
static NEOERR * _builtin_last(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args,
                               CSARG *result)
{
  NEOERR *err;
  CS_LOCAL_MAP *map;
  char *c;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  /* default is "not last" */
  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  /* Only applies to possible local vars */
  if ((val.op_type & CS_TYPE_VAR) && !strchr(val.s, '.'))
  {
    map = lookup_map (parse, val.s, &c);
    if (map) {
      if (map->last) {
        result->n = 1;
      } else if (map->type == CS_TYPE_VAR) {
        if (hdf_obj_next(map->h) == NULL) {
          result->n = 1;
        }
      }
    }
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}

/* returns the absolute value (ie, positive) of a number */
static NEOERR * _builtin_abs (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args,
                              CSARG *result)
{
  NEOERR *err;
  int n1 = 0;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  result->op_type = CS_TYPE_NUM;
  n1 = arg_eval_num(parse, &val);
  result->n = abs(n1);

  if (val.alloc) free(val.s);
  return STATUS_OK;
}

/* returns the larger or two integers */
static NEOERR * _builtin_max (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args,
                              CSARG *result)
{
  NEOERR *err;
  long int n1 = 0;
  long int n2 = 0;

  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  err = cs_arg_parse(parse, args, "ii", &n1, &n2);
  if (err) return nerr_pass(err);
  result->n = (n1 > n2) ? n1 : n2;

  return STATUS_OK;
}

/* returns the smaller or two integers */
static NEOERR * _builtin_min (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args,
                              CSARG *result)
{
  NEOERR *err;
  long int n1 = 0;
  long int n2 = 0;

  result->op_type = CS_TYPE_NUM;
  result->n = 0;

  err = cs_arg_parse(parse, args, "ii", &n1, &n2);
  if (err) return nerr_pass(err);
  result->n = (n1 < n2) ? n1 : n2;

  return STATUS_OK;
}

static NEOERR * _builtin_str_slice (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  char *s = NULL;
  char *slice;
  long int b = 0;
  long int e = 0;
  size_t len;

  result->op_type = CS_TYPE_STRING;
  result->s = "";

  err = cs_arg_parse(parse, args, "sii", &s, &b, &e);
  if (err) return nerr_pass(err);
  /* If null, return empty string */
  if (s == NULL) return STATUS_OK;
  len = strlen(s);
  if (b < 0 && e == 0) e = len;
  if (b < 0) b += len;
  if (e < 0) e += len;
  if (e > len) e = len;
  /* Its the whole string */
  if (b == 0 && e == len)
  {
    result->s = s;
    result->alloc = 1;
    return STATUS_OK;
  }
  if (e < b) b = e;
  if (b == e)
  {
    /* If null, return empty string */
    free(s);
    return STATUS_OK;
  }
  slice = (char *) malloc (sizeof(char) * (e-b+1));
  if (slice == NULL)
    return nerr_raise(NERR_NOMEM, "Unable to allocate memory for string slice");
  strncpy(slice, s + b, e-b);
  free(s);
  slice[e-b] = '\0';

  result->s = slice;
  result->alloc = 1;

  return STATUS_OK;
}

#ifdef ENABLE_GETTEXT
static NEOERR * _builtin_gettext(CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  char *s;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  result->op_type = CS_TYPE_STRING;
  result->s = "";

  if (val.op_type & (CS_TYPE_VAR | CS_TYPE_STRING))
  {
    s = arg_eval(parse, &val);
    if (s)
    {
      result->s = gettext(s);
    }
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}
#endif

static NEOERR * _str_func_wrapper (CSPARSE *parse, CS_FUNCTION *csf, CSARG *args, CSARG *result)
{
  NEOERR *err;
  char *s;
  CSARG val;

  memset(&val, 0, sizeof(val));
  err = eval_expr(parse, args, &val);
  if (err) return nerr_pass(err);

  if (val.op_type & (CS_TYPE_VAR | CS_TYPE_STRING))
  {
    result->op_type = CS_TYPE_STRING;
    result->n = 0;

    s = arg_eval(parse, &val);
    if (s)
    {
      err = csf->str_func(s, &(result->s));
      if (err) return nerr_pass(err);
      result->alloc = 1;
    }
  }
  else
  {
    result->op_type = val.op_type;
    result->n = val.n;
    result->s = val.s;
    result->alloc = val.alloc;
    val.alloc = 0;
  }
  if (val.alloc) free(val.s);
  return STATUS_OK;
}

NEOERR *cs_register_strfunc(CSPARSE *parse, char *funcname, CSSTRFUNC str_func)
{
  NEOERR *err;

  err = cs_register_function(parse, funcname, 1, _str_func_wrapper);
  if (err) return nerr_pass(err);
  parse->functions->str_func = str_func;

  return STATUS_OK;
}

NEOERR *cs_register_esc_strfunc(CSPARSE *parse, char *funcname,
                                CSSTRFUNC str_func)
{
  NEOERR *err;

  err = cs_register_strfunc(parse, funcname, str_func);
  if (err) return nerr_pass(err);
  parse->functions->escape = NEOS_ESCAPE_FUNCTION;

  return STATUS_OK;
}

/* **** CS Initialize/Destroy ************************************ */
NEOERR *cs_init (CSPARSE **parse, HDF *hdf) {
  return nerr_pass(cs_init_internal(parse, hdf, NULL));
}

static NEOERR *cs_init_internal (CSPARSE **parse, HDF *hdf, CSPARSE *parent)
{
  NEOERR *err = STATUS_OK;
  CSPARSE *my_parse;
  STACK_ENTRY *entry;
  char *esc_value;
  CS_ESCAPE_MODES *esc_cursor;

  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), my_parse);
  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;
  entry->escape = NEOS_ESCAPE_NONE;
  err = uListAppend(my_parse->stack, entry);
  if (err != STATUS_OK) {
    free (entry);
    cs_destroy(&my_parse);
    return nerr_pass(err);
  }
  my_parse->tag = hdf_get_value(hdf, "Config.TagStart", "cs");
  my_parse->taglen = strlen(my_parse->tag);
  my_parse->hdf = hdf;

  /* Let's set the default escape data */
  my_parse->escaping.global_ctx = NEOS_ESCAPE_NONE;
  my_parse->escaping.next_stack = NEOS_ESCAPE_NONE;
  my_parse->escaping.when_undef = NEOS_ESCAPE_NONE;

  /* See CS_ESCAPE_MODES. 0 is "none" */
  esc_value = hdf_get_value(hdf, "Config.VarEscapeMode", EscapeModes[0].mode);
  /* Let's ensure the specified escape mode is valid and proceed */
  for (esc_cursor = &EscapeModes[0];
       esc_cursor->mode != NULL;
       esc_cursor++)
    if (!strcmp(esc_value, esc_cursor->mode))
    {
      my_parse->escaping.global_ctx = esc_cursor->context;
      my_parse->escaping.next_stack = esc_cursor->context;
      entry->escape = esc_cursor->context;
      break;
    }
  /* Didn't find an acceptable value we were looking for */
  if (esc_cursor->mode == NULL) {
    cs_destroy (&my_parse);
    return nerr_raise (NERR_OUTOFRANGE,
      "Invalid HDF value for Config.VarEscapeMode (none,html,js,url): %s",
      esc_value);
  }

  /* Read configuration value to determine whether to enable audit mode */
  my_parse->audit_mode = hdf_get_int_value(hdf, "Config.EnableAuditMode", 0);

  my_parse->err_list = NULL;

  if (parent == NULL)
  {
    static struct _builtin_functions {
      const char *name;
      int nargs;
      CSFUNCTION function;
    } Builtins[] = {
      { "len", 1, _builtin_subcount },
      { "subcount", 1, _builtin_subcount },
      { "name", 1, _builtin_name },
      { "first", 1, _builtin_first },
      { "last", 1, _builtin_last },
      { "abs", 1, _builtin_abs },
      { "max", 2, _builtin_max },
      { "min", 2, _builtin_min },
      { "string.find", 2, _builtin_str_find },
      { "string.slice", 3, _builtin_str_slice },
      { "string.length", 1, _builtin_str_length },
      { "string.crc", 1, _builtin_str_crc},
#ifdef ENABLE_GETTEXT
      { "_", 1, _builtin_gettext },
#endif
      { NULL, 0, NULL },
    };
    int x = 0;
    while (Builtins[x].name != NULL) {
      err = cs_register_function(my_parse, Builtins[x].name, Builtins[x].nargs,
                               Builtins[x].function);
      if (err)
      {
        cs_destroy(&my_parse);
        return nerr_pass(err);
      }
      x++;
    }
    /* Set global_hdf to be null */
    my_parse->global_hdf = NULL;
    my_parse->parent = NULL;
  }
  else
  {
    /* TODO: macros and functions should actually not be duplicated, they
     * should just be modified in lookup to walk the CS struct hierarchy we're
     * creating here */
    /* BUG: We currently can't copy the macros because they reference the parse
     * tree, so if this sub-parse tree adds a macro, the macro reference will
     * persist, but the parse tree it points to will be gone when the sub-parse
     * is gone. */
    my_parse->functions = parent->functions;
    my_parse->global_hdf = parent->global_hdf;
    my_parse->fileload = parent->fileload;
    my_parse->fileload_ctx = parent->fileload_ctx;
    // This should be safe since locals handling is done entirely local to the
    // eval functions, not globally by the parse handling.  This should
    // pass the locals down to the new parse context to make locals work with
    // lvar
    my_parse->locals = parent->locals;
    my_parse->parent = parent;

    /* Copy the audit flag from parent */
    my_parse->audit_mode = parent->audit_mode;
  }

  *parse = my_parse;
  return STATUS_OK;
}

void cs_register_fileload(CSPARSE *parse, void *ctx, CSFILELOAD fileload) {
  if (parse != NULL) {
    parse->fileload_ctx = ctx;
    parse->fileload = fileload;
  }
}

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));
  if (my_parse->parent == NULL) {
    dealloc_function(&(my_parse->functions));
  }

  /* Free list of errors */
  if (my_parse->err_list != NULL) {
    CS_ERROR *ptr;

    while (my_parse->err_list) {
      ptr = my_parse->err_list->next;
      free(my_parse->err_list->err);
      free(my_parse->err_list);
      my_parse->err_list = ptr;
    }
  }

  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)
{
  NEOERR *err;

  while (node != NULL)
  {
    snprintf (buf, blen, "%*s %s ", depth, "", Commands[node->cmd].cmd);
    err = cb (ctx, buf);
    if (err) return nerr_pass (err);
    if (node->cmd)
    {
      if (node->arg1.op_type)
      {
	if (node->arg1.op_type == CS_TYPE_NUM)
	{
	  snprintf (buf, blen, "%ld ", node->arg1.n);
	}
	else if (node->arg1.op_type == CS_TYPE_MACRO)
	{
	  snprintf (buf, blen, "%s ", node->arg1.macro->name);
	}
	else
	{
	  snprintf (buf, blen, "%s ", node->arg1.s);
	}
	err = cb (ctx, buf);
	if (err) return nerr_pass (err);
      }
      if (node->arg2.op_type)
      {
	if (node->arg2.op_type == CS_TYPE_NUM)
	{
	  snprintf (buf, blen, "%ld", node->arg2.n);
	}
	else
	{
	  snprintf (buf, blen, "%s", node->arg2.s);
	}
	err = cb (ctx, buf);
	if (err) return nerr_pass (err);
      }
      if (node->vargs)
      {
	CSARG *arg;
	arg = node->vargs;
	while (arg)
	{
	  if (arg->op_type == CS_TYPE_NUM)
	  {
	    snprintf (buf, blen, "%ld ", arg->n);
	  }
	  else
	  {
	    snprintf (buf, blen, "%s ", arg->s);
	  }
	  err = cb (ctx, buf);
	  if (err) return nerr_pass (err);
	  arg = arg->next;
	}
      }
    }
    err = cb (ctx, "\n");
    if (err) return nerr_pass (err);
    if (node->case_0)
    {
      snprintf (buf, blen, "%*s %s\n", depth, "", "Case 0");
      err = cb (ctx, buf);
      if (err) return nerr_pass (err);
      err = dump_node (parse, node->case_0, depth+1, ctx, cb, buf, blen);
      if (err) return nerr_pass (err);
    }
    if (node->case_1)
    {
      snprintf (buf, blen, "%*s %s\n", depth, "", "Case 1");
      err = cb (ctx, buf);
      if (err) return nerr_pass (err);
      err = dump_node (parse, node->case_1, depth+1, ctx, cb, buf, blen);
      if (err) return nerr_pass (err);
    }
    node = node->next;
  }
  return STATUS_OK;
}

NEOERR *cs_dump (CSPARSE *parse, void *ctx, CSOUTFUNC cb)
{
  CSTREE *node;
  char buf[4096];

  if (parse->tree == NULL)
    return nerr_raise (NERR_ASSERT, "No parse tree exists");

  node = parse->tree;
  return nerr_pass (dump_node (parse, node, 0, ctx, cb, buf, sizeof(buf)));
}

#if 0
static char *node_name (CSTREE *node)
{
  static char buf[256];

  if (node == NULL)
    snprintf (buf, sizeof(buf), "NULL");
  else
    snprintf (buf, sizeof(buf), "%s_%08x", Commands[node->cmd].cmd,
	node->node_num);

  return buf;
}

static NEOERR *dump_node_pre_c (CSPARSE *parse, CSTREE *node, FILE *fp)
{
  NEOERR *err;

  while (node != NULL)
  {
    fprintf (fp, "CSTREE %s;\n", node_name(node));
    if (node->case_0)
    {
      err = dump_node_pre_c (parse, node->case_0, fp);
      if (err != STATUS_OK) nerr_pass (err);
    }
    if (node->case_1)
    {
      err = dump_node_pre_c (parse, node->case_1, fp);
      if (err != STATUS_OK) nerr_pass (err);
    }
    node = node->next;
  }
  return STATUS_OK;
}

static NEOERR *dump_node_c (CSPARSE *parse, CSTREE *node, FILE *fp)
{
  NEOERR *err;
  char *s;

  while (node != NULL)
  {
    fprintf (fp, "CSTREE %s =\n\t{%d, %d, %d, ", node_name(node), node->node_num,
	node->cmd, node->flags);
    s = repr_string_alloc (node->arg1.s);
    if (s == NULL)
      return nerr_raise(NERR_NOMEM, "Unable to allocate space for repr");
    fprintf (fp, "\n\t  { %d, %s, %ld }, ", node->arg1.op_type, s, node->arg1.n);
    free(s);
    s = repr_string_alloc (node->arg2.s);
    if (s == NULL)
      return nerr_raise(NERR_NOMEM, "Unable to allocate space for repr");
    fprintf (fp, "\n\t  { %d, %s, %ld }, ", node->arg2.op_type, s, node->arg2.n);
    free(s);
    if (node->case_0)
      fprintf (fp, "\n\t%d, &%s, ", node->op, node_name(node->case_0));
    else
      fprintf (fp, "\n\t%d, NULL, ", node->op);
    if (node->case_1)
      fprintf (fp, "&%s, ", node_name(node->case_1));
    else
      fprintf (fp, "NULL, ");
    if (node->next)
      fprintf (fp, "&%s};\n\n", node_name(node->next));
    else
      fprintf (fp, "NULL};\n\n");
    if (node->case_0)
    {
      err = dump_node_c (parse, node->case_0, fp);
      if (err != STATUS_OK) nerr_pass (err);
    }
    if (node->case_1)
    {
      err = dump_node_c (parse, node->case_1, fp);
      if (err != STATUS_OK) nerr_pass (err);
    }
    node = node->next;
  }
  return STATUS_OK;
}

NEOERR *cs_dump_c (CSPARSE *parse, char *path)
{
  CSTREE *node;
  FILE *fp;
  NEOERR *err;

  if (parse->tree == NULL)
    return nerr_raise (NERR_ASSERT, "No parse tree exists");

  fp = fopen(path, "w");
  if (fp == NULL)
  {
    return nerr_raise (NERR_SYSTEM,
	"Unable to open file %s for writing: [%d] %s", path, errno,
	strerror(errno));
  }

  fprintf(fp, "/* Auto-generated file: DO NOT EDIT */\n");
  fprintf(fp, "#include <stdlib.h>\n\n");
  fprintf(fp, "#include \"cs.h\"\n");
  node = parse->tree;
  err = dump_node_pre_c (parse, node, fp);
  fprintf(fp, "\n");
  err = dump_node_c (parse, node, fp);
  fclose(fp);
  return nerr_pass (err);
}
#endif