/* gib_parse.c GIB parser functions Copyright (C) 2002 Brian Koropoff Author: Brian Koropoff Date: #DATE# This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include "QF/sys.h" #include "QF/dstring.h" #include "QF/va.h" #include "QF/cmd.h" #include "QF/cbuf.h" #include "QF/gib.h" #include "gib_parse.h" #include "gib_process.h" #include "gib_semantics.h" /* GIB_Escaped Returns true if character i in str is escaped with a backslash (and the backslash is not itself escaped). */ inline qboolean GIB_Escaped (const char *str, int i) { int n, c; if (!i) return 0; for (n = i - 1, c = 0; n >= 0 && str[n] == '\\'; n--, c++); return c & 1; } /* GIB_Parse_Match_* These are the workhorses of the GIB parser. They iterate an index variable through a string until an appropriate matching character is found, calling themselves and their neighbors recursively to handle sections of string that they are uninterested in. FIXME: Make sure everything is calling everything else it might need to. Make appropriate functions intolerant of newlines. */ static char GIB_Parse_Match_Dquote (const char *str, unsigned int *i) { unsigned int n = *i; for ((*i)++; str[*i]; (*i)++) { if (str[*i] == '\n') break; else if (str[*i] == '\"' && !GIB_Escaped (str, *i)) return 0; } *i = n; return '\"'; } char GIB_Parse_Match_Brace (const char *str, unsigned int *i) { char c; unsigned int n = *i; for ((*i)++; str[*i]; (*i)++) { if (str[*i] == '\"') { if ((c = GIB_Parse_Match_Dquote (str, i))) return c; } else if (str[*i] == '{') { if ((c = GIB_Parse_Match_Brace (str, i))) return c; } else if (str[*i] == '}') return 0; } *i = n; return '{'; } char GIB_Parse_Match_Paren (const char *str, unsigned int *i) { char c; unsigned int n = *i; for ((*i)++; str[*i]; (*i)++) { if (str[*i] == '(') { if ((c = GIB_Parse_Match_Paren (str, i))) return c; } else if (str[*i] == '\"') { if ((c = GIB_Parse_Match_Dquote (str, i))) return c; } else if (str[*i] == ')') return 0; } *i = n; return '('; } char GIB_Parse_Match_Backtick (const char *str, unsigned int *i) { char c; unsigned int n = *i; for ((*i)++; str[*i]; (*i)++) { if (str[*i] == '`') return 0; else if (str[*i] == '\"') { // Skip over strings as usual if ((c = GIB_Parse_Match_Dquote (str, i))) return c; } } *i = n; return '`'; } char GIB_Parse_Match_Index (const char *str, unsigned int *i) { char c; unsigned int n = *i; for ((*i)++; str[*i]; (*i)++) { if (str[*i] == '[' && (c = GIB_Parse_Match_Index (str, i))) return c; else if (str[*i] == ']') return 0; } *i = n; return '['; } char GIB_Parse_Match_Var (const char *str, unsigned int *i) { char c; (*i)++; if (str[*i] == '{' && (c = GIB_Parse_Match_Brace (str, i))) return c; else { for (; isalnum ((byte) str[*i]) || str[*i] == '_'; (*i)++); if (str[*i] == '[') { if ((c = GIB_Parse_Match_Index (str, i))) return c; (*i)++; } } return 0; } VISIBLE qboolean gib_parse_error; unsigned int gib_parse_error_pos; const char *gib_parse_error_msg; void GIB_Parse_Error (const char *msg, unsigned int pos) { gib_parse_error = true; gib_parse_error_msg = msg; gib_parse_error_pos = pos; } const char * GIB_Parse_ErrorMsg (void) { return gib_parse_error_msg; } unsigned int GIB_Parse_ErrorPos (void) { return gib_parse_error_pos; } static gib_tree_t * GIB_Parse_Tokens (const char *program, unsigned int *i, unsigned int pofs) { char c = 0, delim, *str; unsigned int tstart, start; gib_tree_t *nodes = 0, *cur, *new; gib_tree_t **node = &nodes; enum {CAT_NORMAL = 0, CAT_DISALLOW, CAT_CONCAT} cat = CAT_DISALLOW; const char *catestr = "Comma found before first argument, nothing to concatenate to."; gib_parse_error = false; while (1) { // Skip whitespace while (program[*i] != '\n' && isspace ((byte) program[*i])) (*i)++; // Check for concatenation, skip comma and any more whitespace if (program[*i] == ',') { if (cat == CAT_DISALLOW) { GIB_Parse_Error(catestr, *i + pofs); goto ERROR; } cat = CAT_CONCAT; (*i)++; while (program[*i] != '\n' && isspace ((byte) program[*i])) (*i)++; if (program[*i] == ',') { GIB_Parse_Error("Double comma error.", *i+pofs); goto ERROR; } } else cat = CAT_NORMAL; // New line/command? if (!program[*i] || program[*i] == '\n' || program[*i] == ';') break; // Save our start position start = *i; tstart = start + 1; delim = program[*i]; switch (delim) { case '{': if ((c = GIB_Parse_Match_Brace (program, i))) goto ERROR; break; case '\"': if ((c = GIB_Parse_Match_Dquote (program, i))) goto ERROR; break; case '(': if ((c = GIB_Parse_Match_Paren (program, i))) goto ERROR; break; default: // Find the end of a "normal" token delim = ' '; tstart = *i; for (; program[*i] && !isspace ((byte) program[*i]) && program[*i] != ',' && program[*i] != ';'; (*i)++) { if (program[*i] == '{') { if ((c = GIB_Parse_Match_Brace (program, i))) goto ERROR; } else if (program[*i] == '(') { if ((c = GIB_Parse_Match_Paren (program, i))) goto ERROR; } else if (program[*i] == '`') { if ((c = GIB_Parse_Match_Backtick (program, i))) goto ERROR; // Handle comments } else if (program[*i] == '/' && program[*i + 1] == '/') { for ((*i) += 2; program[*i] && program[*i] != '\n'; (*i)++); goto DONE; } } } c = 0; cur = *node = GIB_Tree_New (TREE_T_ARG); cur->start = start + pofs; cur->end = *i + pofs; cur->delim = delim; str = calloc (*i - tstart + 1, sizeof (char)); cur->str = str; memcpy (str, program + tstart, *i - tstart); if (cur->delim == '{') { if (cat == CAT_CONCAT) { GIB_Parse_Error ("Program blocks may not be concatenated with other arguments.", start + pofs); goto ERROR; } catestr = "Program blocks may not be concatenated with other arguments."; cat = CAT_DISALLOW; // Try to parse sub-program if (!(new = GIB_Parse_Lines (str, tstart + pofs))) goto ERROR; cur->children = new; // Check for embedded commands/variables } else if (cur->delim == ' ' || cur->delim == '(') { if (cur->delim == ' ' && (str[0] == '@' || str[0] == '%')) { if (cat == CAT_CONCAT) { GIB_Parse_Error ("Variable expansions may not be concatenated with other arguments.", start + pofs); goto ERROR; } catestr = "Variable expansions may not be concatenated with other arguments."; cat = CAT_DISALLOW; cur->flags |= TREE_A_EXPAND; } // We can handle escape characters now } else if (cur->delim == '\"') GIB_Process_Escapes (str); if (cat == CAT_CONCAT) cur->flags |= TREE_A_CONCAT; // Nothing left to parse? if (!program[*i]) break; // On non-normal tokens, move past the delimeter if (cur->delim != ' ') (*i)++; node = &cur->next; } DONE: return nodes; ERROR: if (c) GIB_Parse_Error (va ("Could not find match for '%c'.", c), *i + pofs); if (nodes) GIB_Tree_Unref (&nodes); return 0; } gib_tree_t * GIB_Parse_Lines (const char *program, unsigned int pofs) { unsigned int i = 0, lstart; gib_tree_t *lines = 0, *cur, *tokens, **line = &lines, *temp; char *str; while (1) { while (isspace ((byte) program[i]) || program[i] == ';') i++; if (!program[i]) break; lstart = i; // If we parse something useful... if ((tokens = GIB_Parse_Tokens (program, &i, pofs))) { str = calloc (i - lstart + 1, sizeof(char)); memcpy (str, program + lstart, i - lstart); // All settings for the line must be passed here cur = GIB_Semantic_Tokens_To_Lines ( tokens, str, 0, lstart+pofs, i+pofs ); if (gib_parse_error) { free (str); goto ERROR; } // Find last line for (temp = cur; temp->next; temp = temp->next); // Link it to lines we already have *line = cur; line = &temp->next; } if (gib_parse_error) goto ERROR; } return lines; ERROR: if (lines) GIB_Tree_Unref (&lines); return 0; } gib_tree_t * GIB_Parse_Embedded (gib_tree_t *token) { unsigned int i, n, t, start, end; char c, d, *str; const char *program = token->str; gib_tree_t *lines = 0, *cur, *tokens, **metanext, *temp; gib_parse_error = false; metanext = &token->children; for (i = 0; program[i]; i++) { if (program[i] == '`' || (program[i] == '$' && program[i + 1] == '(')) { // Extract the embedded command start = i; if (program[i] == '`') { n = i + 1; if ((c = GIB_Parse_Match_Backtick (program, &i))) goto ERROR; } else { n = ++i + 1; if ((c = GIB_Parse_Match_Paren (program, &i))) goto ERROR; } end = i + 1; // Construct the actual line to be executed // Reset error condition c = 0; // Location to begin parsing from t = 0; // Extract embedded command into a new string str = calloc (end - start + 1, sizeof(char)); memcpy (str, program + n, i - n); // Attempt to parse tokens from it tokens = GIB_Parse_Tokens (str, &t, start + token->start); if (!tokens) { free (str); goto ERROR; } // All settings for the line must be passed here cur = GIB_Semantic_Tokens_To_Lines ( tokens, str, TREE_L_EMBED, start + token->start, end + token->start ); if (gib_parse_error) { free (str); goto ERROR; } // Find last line for (temp = cur; temp->next; temp = temp->next); // Link it to lines we already have // (new lines are added to the start) temp->next = lines; lines = cur; // Create a representative child node for // GIB_Process_Embedded to use cur = GIB_Tree_New (TREE_T_META); cur->delim = '`'; // Save start/end indices cur->start = start; cur->end = end; // Link it to end of children list for token we are processing *metanext = cur; metanext = &cur->next; // Check for variable substitution } else if (program[i] == '$' || program[i] == '#') { // Extract variable name start = i; end = 0; d = program[i]; if (program[i + 1] == '{') { n = i + 2; end++; } else n = i + 1; if ((c = GIB_Parse_Match_Var (program, &i))) goto ERROR; end += i; cur = GIB_Tree_New (TREE_T_META); cur->delim = d; str = calloc (i - n + 1, sizeof (char)); memcpy (str, program + n, i - n); cur->str = str; // Can we use the name as is, or must processing be done at // runtime? if (strchr (str, '$') || strchr (str, '#')) cur->flags |= TREE_A_EMBED; // Save start/end indices cur->start = start; cur->end = end; *metanext = cur; metanext = &cur->next; // Don't skip anything important if (program[n - 1] != '{') i--; } } return lines; ERROR: if (c) GIB_Parse_Error (va ("Could not find match for '%c'.", c), i + token->start); if (lines) GIB_Tree_Unref (&lines); return 0; }