/* Copyright (C) 2004, 2005, 2006, 2007 John E. Davis This file is part of the S-Lang Library. The S-Lang Library 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. The S-Lang Library 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 library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /*--------------------------------*-C-*---------------------------------* * File: slprepr.c * * preprocessing routines */ /*{{{ notes: */ /* * various preprocessing tokens supported * * #ifdef TOKEN1 TOKEN2 ... * - True if any of TOKEN1 TOKEN2 ... are defined * * #ifndef TOKEN1 TOKEN2 ... * - True if none of TOKEN1 TOKEN2 ... are defined * * #iftrue * #ifnfalse * #if true * #if !false * - always True * * #iffalse * #ifntrue * #if false * #if !true * - always False * * #if$ENV * - True if the enviroment variable ENV is set * * #ifn$ENV * #if !$ENV * - True if the enviroment variable ENV is not set * * #if$ENV TOKEN1 TOKEN2 ... * - True if the contents of enviroment variable ENV match * any of TOKEN1 TOKEN2 ... * * #ifn$ENV TOKEN1 TOKEN2 ... * #if !$ENV TOKEN1 TOKEN2 ... * - True if the contents of enviroment variable ENV do not match * any of TOKEN1 TOKEN2 ... * * NB: For $ENV, the tokens may contain wildcard characters: * '?' - match any single character * '*' - match any number of characters * * #ifexists TOKEN * #ifnexists TOKEN * #if !exists TOKEN * - check if a variable/function exists * * #ifeval EXPRESSION * #ifneval EXPRESSION * #if !eval TOKEN * - evaluates the EXPRESSION as an SLang expression * * #if (EXPRESSION) * #if !(EXPRESSION) * - as per '#ifeval' / '#ifneval', * evaluates the EXPRESSION as a SLang expression * - using '#ifn (EXPRESSION)' is possible, but deprecated * * #elif... * #else * #endif * * #stop * - stop reading the entire file here, provided that the line * would have been executed * eg: * #iffalse * # stop * #endif * would NEVER stop * * # * - start embedded text region * # * - end embedded text region * * All text, include other preprocessing directives, that occurs between * the '#' and '#' directives will be ignored. * This is useful for embedding other code or documentation. * eg: * # * \chapter{My Documentation Effort} * # * NB: * although the current implementation only looks for sequences * '#<' and '#' form * for documentation purposes and to avoid future surprises. * * do NOT attempt to nest these constructions * * GENERAL NOTES: * Apart from the '#ifdef' and '#ifndef' constructions, we are quite * generous with accepting whitespace and the alternative '!' form. * eg., * #ifTEST * #ifnTEST * #if TEST * #if !TEST * #if ! TEST * * mj olesen *----------------------------------------------------------------------*/ /*}}}*/ /*{{{ includes: */ #include "slinclud.h" #include "slang.h" #include "_slang.h" /*}}}*/ int (*SLprep_exists_hook) (char *, char); /* in "slang.h" */ int (*_pSLprep_eval_hook) (char *); /* in "_slang.h" */ struct _pSLprep_Type { int this_level; int exec_level; int prev_exec_level; char *prefix; unsigned int prefix_len; char *comment_start; char *comment_stop; unsigned int comment_start_len; unsigned int flags; #define SLPREP_USER_FLAGS_MASK 0x00FF #define SLPREP_STOP_READING 0x0100 #define SLPREP_EMBEDDED_TEXT 0x0200 int (*exists_hook) (SLprep_Type *, char *); int (*eval_hook) (SLprep_Type *, char *); }; int SLprep_set_eval_hook (SLprep_Type *pt, int (*f)(SLprep_Type *, char *)) { if (pt == NULL) return -1; pt->eval_hook = f; return 0; } int SLprep_set_exists_hook (SLprep_Type *pt, int (*f)(SLprep_Type *, char *)) { if (pt == NULL) return -1; pt->exists_hook = f; return 0; } void SLprep_delete (SLprep_Type *pt) { if (pt == NULL) return; /* NULLs ok */ SLang_free_slstring (pt->comment_start); SLang_free_slstring (pt->comment_stop); SLang_free_slstring (pt->prefix); SLfree ((char *) pt); } int SLprep_set_comment (SLprep_Type *pt, char *start, char *stop) { if ((pt == NULL) || (start == NULL)) return -1; if (NULL == (start = SLang_create_slstring (start))) return -1; if (stop == NULL) stop = ""; if (NULL == (stop = SLang_create_slstring (stop))) { SLang_free_slstring (start); return -1; } if (pt->comment_start != NULL) SLang_free_slstring (pt->comment_start); pt->comment_start = start; pt->comment_start_len = strlen (start); if (pt->comment_stop != NULL) SLang_free_slstring (pt->comment_stop); pt->comment_stop = stop; return 0; } int SLprep_set_prefix (SLprep_Type *pt, char *prefix) { if ((pt == NULL) || (prefix == NULL)) return -1; if (NULL == (prefix = SLang_create_slstring (prefix))) return -1; if (pt->prefix != NULL) SLang_free_slstring (pt->prefix); pt->prefix = prefix; pt->prefix_len = strlen (prefix); return 0; } /*{{{ SLprep_open_prep (), SLprep_close_prep () */ SLprep_Type *SLprep_new (void) { SLprep_Type *pt; if (NULL == (pt = (SLprep_Type *)SLcalloc (1, sizeof (SLprep_Type)))) return NULL; if (-1 == SLprep_set_comment (pt, "%", "")) { SLprep_delete (pt); return NULL; } if (-1 == SLprep_set_prefix (pt, "#")) { SLprep_delete (pt); return NULL; } return pt; } int SLprep_set_flags (SLprep_Type *pt, unsigned int flags) { if (pt == NULL) return -1; flags &= SLPREP_USER_FLAGS_MASK; pt->flags &= ~SLPREP_USER_FLAGS_MASK; pt->flags |= flags; return 0; } /*}}}*/ /*{{{ SLwildcard () */ /*----------------------------------------------------------------------* * Does `string' match `pattern' ? * * '*' in pattern matches any sub-string (including the null string) * '?' matches any single char. * * Code taken from that donated by Paul Hudson * to the fvwm project. * It is public domain, no strings attached. No guarantees either. *----------------------------------------------------------------------*/ static int SLwildcard (char *pattern, char *string) { if (pattern == NULL || *pattern == '\0' || !strcmp (pattern, "*")) return 1; else if (string == NULL) return 0; while (*pattern && *string) switch (*pattern) { case '?': /* match any single character */ pattern++; string++; break; case '*': /* see if rest of pattern matches any trailing */ /* substring of the string. */ if (*++pattern == '\0') return 1; /* trailing * must match rest */ while (*string) { if (SLwildcard (pattern, string)) return 1; string++; } return 0; /* break; */ default: if (*pattern == '\\') { if (*++pattern == '\0') pattern--; /* don't skip trailing backslash */ } if (*pattern++ != *string++) return 0; break; } return ((*string == '\0') && ((*pattern == '\0') || !strcmp (pattern, "*"))); } /*}}}*/ #if defined(__16_BIT_SYSTEM__) # define MAX_DEFINES 10 #else # define MAX_DEFINES 128 #endif /* The extra one is for NULL termination */ char *_pSLdefines [MAX_DEFINES + 1]; int SLdefine_for_ifdef (char *s) /*{{{*/ { unsigned int i; for (i = 0; i < MAX_DEFINES; i++) { char *s1 = _pSLdefines [i]; if (s1 == s) return 0; /* already defined (hashed string) */ if (s1 != NULL) continue; s = SLang_create_slstring (s); if (s == NULL) return -1; _pSLdefines[i] = s; return 0; } return -1; } /*}}}*/ /*{{{ static functions */ static int is_any_defined (SLprep_Type *pt, char *buf) /*{{{*/ { char *sys; unsigned int i; char comment; /* FIXME: priority: low. This needs adapted to multi-char comments */ comment = pt->comment_start[0]; while (1) { register char ch; /* Skip whitespace */ while (((ch = *buf) == ' ') || (ch == '\t')) buf++; if ((ch == '\n') || (ch == 0) || (ch == comment)) return 0; i = 0; while (NULL != (sys = _pSLdefines [i++])) { unsigned int n; if (*sys != ch) continue; n = strlen (sys); if (0 == strncmp (buf, sys, n)) { char ch1 = *(buf + n); if ((ch1 == '\n') || (ch1 == 0) || (ch1 == ' ') || (ch1 == '\t') || (ch == comment)) return 1; } } /* Skip past word */ while (((ch = *buf) != ' ') && (ch != '\n') && (ch != 0) && (ch != '\t') && (ch != comment)) buf++; } } /*}}}*/ static unsigned char *tokenize (unsigned char *buf, char *token, unsigned int len) { register char *token_end; token_end = token + (len - 1); /* allow room for \0 */ while ((token < token_end) && (*buf > ' ')) *token++ = *buf++; if (*buf > ' ') return NULL; /* token too long */ *token = '\0'; while ((*buf == ' ') || (*buf == '\t')) buf++; return buf; } static int is_env_defined (SLprep_Type *pt, char *buf) /*{{{*/ { char *env, token [128]; char comment = pt->comment_start[0]; /* FIXME: priority: low. This needs adapted to multi-char comments */ if (((unsigned char)*buf <= ' ') || (*buf == comment)) return 0; /* no token */ if (NULL == (buf = (char *) tokenize ((unsigned char *) buf, token, sizeof (token)))) return 0; if (NULL == (env = getenv (token))) return 0; /* ENV not defined */ if ((*buf == '\0') || (*buf == '\n') || (*buf == comment)) return 1; /* no tokens, but getenv() worked */ do { buf = (char *) tokenize ((unsigned char *) buf, token, sizeof (token)); if (buf == NULL) return 0; if (SLwildcard (token, env)) return 1; } while (*buf && (*buf != '\n') && (*buf != comment)); return 0; } /*}}}*/ /*}}}*/ int SLprep_line_ok (char *buf, SLprep_Type *pt) /*{{{*/ { int level, prev_exec_level, exec_level; unsigned int flags; if ((buf == NULL) || (pt == NULL)) return 1; /* the '#stop' marker was already reached */ if (pt->flags & SLPREP_STOP_READING) return 0; /* local bookkeeping */ level = pt->this_level; exec_level = pt->exec_level; prev_exec_level = pt->prev_exec_level; flags = pt->flags; if ((*buf != pt->prefix[0]) || (0 != strncmp (buf, pt->prefix, pt->prefix_len))) { /* ignore out-of-context or embedded text */ if ((level != exec_level) || (flags & SLPREP_EMBEDDED_TEXT)) return 0; if (*buf == '\n') return (0 != (flags & SLPREP_BLANK_LINES_OK)); if ((*buf == pt->comment_start[0]) && (0 == strncmp (buf, pt->comment_start, pt->comment_start_len))) return (0 != (flags & SLPREP_COMMENT_LINES_OK)); return 1; } buf += pt->prefix_len; /* * Always allow '#!' to pass. This could be a shell script * with something like '#! /usr/local/bin/slang' */ if ((*buf == '!') && (pt->prefix[0] == '#') && (pt->prefix_len == 1)) return 0; /* Allow whitespace as in '# ifdef' */ while ((*buf == ' ') || (*buf == '\t')) buf++; /* * quick and dirty coding for '#' and '#' * only bothers to differentiate between '#<' and '#' */ pt->flags &= ~SLPREP_EMBEDDED_TEXT; else /* likely a '#' */ pt->flags |= SLPREP_EMBEDDED_TEXT; return 0; } if (pt->flags & SLPREP_EMBEDDED_TEXT) return 0; /* embedded text - ignore everything */ if ((*buf < 'a') || (*buf > 'z')) /* something weird */ return (level == exec_level); if ( !strncmp(buf, "stop", 4) ) { if (level == exec_level) /* signal stop if we're in scope */ pt->flags |= SLPREP_STOP_READING; return 0; /* swallow this tag */ } if (!strncmp(buf, "endif", 5)) { if (level == exec_level) { exec_level--; prev_exec_level = exec_level; } level--; if (level < prev_exec_level) prev_exec_level = level; goto done; } if ((buf[0] == 'e') && (buf[1] == 'l')) /* else, elifdef, ... */ { if ((level == exec_level + 1) && (prev_exec_level != level)) { /* We are in position to execute */ buf += 2; if ((buf[0] == 's') && (buf[1] == 'e')) { /* 'else' */ exec_level = level; goto done; } /* * drop through to 'if' testing. * First set variable to value appropriate for 'if' testing. */ level--; /* now == to exec level */ } else { if (level == exec_level) { exec_level--; } goto done; } } if ((buf[0] == 'i') && (buf[1] == 'f')) { int test = 0; /* fallback value */ int truth = 1; buf += 2; if (level != exec_level) { level++; goto done; /* Not interested */ } level++; if (buf[0] == 'n') { truth = !truth; buf++; } /* for 'ifdef' and 'ifndef' we are done */ if (!strncmp (buf, "def", 3)) { test = is_any_defined(pt, buf + 3); } else { /* * the '#ifn' construction cannot have whitespace or '!' * for the other forms, we can also accept * 'if!..' instead of 'ifn...', or even 'if ...' or 'if ! ...' */ if (truth) { /* Allow some whitespace */ while ((*buf == ' ') || (*buf == '\t')) buf++; if (*buf == '!') { /* the 'if !' form */ truth = !truth; buf++; while ((*buf == ' ') || (*buf == '\t')) buf++; } } if (*buf == '$') test = is_env_defined (pt, buf + 1); else if ((*buf == '(') && (pt->eval_hook != NULL)) test = pt->eval_hook (pt, buf); else if (!strncmp (buf, "eval", 4) && (pt->eval_hook != NULL)) test = pt->eval_hook (pt, buf + 4); else if (!strncmp (buf, "exists", 6) && (pt->exists_hook != NULL)) test = pt->exists_hook (pt, buf + 6); else if (!strncmp (buf, "true", 4)) test = 1; else if (strncmp (buf, "false", 5)) return 1; /* unknown - let it bomb */ } if (truth == test) prev_exec_level = exec_level = level; } else { return 1; /* let it bomb */ } done: if (exec_level < 0) return 1; /* bad level - let it bomb */ pt->this_level = level; pt->exec_level = exec_level; pt->prev_exec_level = prev_exec_level; return 0; } /*}}}*/ /*{{{ main() - for testing only */ #if 0 int main (int argc, char *argv[]) { int i; char buf[1024]; SLprep_Type pt; SLprep_open_prep (&pt); SLdefine_for_ifdef ("UNIX"); /* super cheap getopts */ for (i = 1; i < argc; i++) { char *p = argv[i]; if (*p++ != '-') break; if (*p == '-') break; if (*p == 'D') { SLdefine_for_ifdef (p + 1); continue; } while (*p) { switch (*p) { case 'B': pt.flags |= (SLPREP_BLANK_LINES_OK); break; case 'C': pt.flags |= (SLPREP_COMMENT_LINES_OK); break; case 'c': if (p[1]) pt.comment_char = *++p; break; case 'p': if (p[1]) pt.preprocess_char = *++p; break; default: fprintf (stderr, "unknown flag '%c'\n", *p); break; } p++; } } while (NULL != fgets (buf, sizeof (buf) - 1, stdin)) { if (SLprep_line_ok (buf, &pt)) fputs (buf, stdout); } SLprep_close_prep (&pt); return 0; } #endif /*}}}*/