/* * 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 #include #include #include #include #include #include #include #include #include #include #ifdef ENABLE_GETTEXT #include #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 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, " 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 *)¤t_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 */ 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; xop_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(¯o); 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(¯o); 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 \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