/* * lexer (lexical analyzer) for control files * * Copyright (C) 1998-2001 D. Hugh Redelmeier. * * 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. See . * * 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. */ #include #include #include #include #include #include #include #include #include "sysdep.h" #include "constants.h" #include "lswlog.h" #include "lex.h" struct file_lex_position *flp = NULL; /* * Open a file for lexical processing. * * new_flp and name must point into storage and will live * at least until the file is closed. * * @param new_flp file position * @param name filename * @param bool optional * @return bool True if successful */ bool lexopen(struct file_lex_position *new_flp, const char *name, bool optional) { FILE *f = fopen(name, "r"); if (f == NULL) { if (!optional || errno != ENOENT) log_errno((e, "could not open \"%s\"", name)); return FALSE; } else { new_flp->previous = flp; flp = new_flp; flp->filename = name; flp->fp = f; flp->lino = 0; flp->bdry = B_none; flp->cur = flp->buffer; /* nothing loaded yet */ flp->under = *flp->cur = '\0'; (void) shift(); /* prime tok */ return TRUE; } } /* * Close filehandle */ void lexclose(void) { fclose(flp->fp); flp = flp->previous; } /* * Token decoding: shift() loads the next token into tok. * If a token starts at the left margin, it is considered * to be the first in a record. We create a special condition, * Record Boundary (analogous to EOF), just before such a token. * We are unwilling to shift through a record boundary: * it must be overridden first. * Returns FALSE if Record Boundary or EOF (i.e. no token); * tok will then be NULL. */ /* * shift - load next token into tok * * @return bool True if successful */ bool shift(void) { char *p = flp->cur; char *sor = NULL; /* start of record for any new lines */ passert(flp->bdry == B_none); *p = flp->under; flp->under = '\0'; for (;;) { switch (*p) { case '\0': /* end of line */ case '#': /* * comment to end of line: treat as end of * line */ /* get the next line */ if (fgets(flp->buffer, sizeof(flp->buffer) - 1, flp->fp) == NULL) { flp->bdry = B_file; flp->tok = flp->cur = NULL; return FALSE; } else { /* strip trailing whitespace, including \n */ for (p = flp->buffer + strlen(flp->buffer); p > flp->buffer && isspace(p[-1]); p--) ; *p = '\0'; flp->lino++; sor = p = flp->buffer; } break; /* try again for a token */ case ' ': /* whitespace */ case '\t': p++; break; /* try again for a token */ case '"': /* quoted token */ case '\'': case '`': /* or execute quotes */ if (p != sor) { /* * we have a quoted token: * note and advance to its end */ flp->tok = p; p = strchr(p + 1, *p); if (p == NULL) { loglog(RC_LOG_SERIOUS, "\"%s\" line %d: unterminated string", flp->filename, flp->lino); p = flp->tok + strlen(flp->tok); } else { p++; /* include delimiter in token */ } /* * remember token delimiter and replace * with '\0' */ flp->under = *p; *p = '\0'; flp->cur = p; return TRUE; } /* FALL THROUGH */ default: if (p != sor) { /* * we seem to have a token: * note and advance to its end */ flp->tok = p; if (p[0] == '0' && p[1] == 't') { /* 0t... token goes to end of line */ p += strlen(p); } else { /* * "ordinary" token: * up to whitespace or end of line */ do { p++; } while (*p != '\0' && !isspace(*p)); /* * fudge to separate ':' from * a preceding adjacent token */ if (p - 1 > flp->tok && p[-1] == ':') p--; } /* * remember token delimiter and replace * with '\0' */ flp->under = *p; *p = '\0'; flp->cur = p; return TRUE; } /* * we have a start-of-record: * return it, deferring "real" token */ flp->bdry = B_record; flp->tok = NULL; flp->under = *p; flp->cur = p; return FALSE; } } } /* * ensures we are at a Record (or File) boundary, optionally warning if not * * @param m string * @return bool True if everything is ok */ bool flushline(const char *m) { if (flp->bdry != B_none) { return TRUE; } else { if (m != NULL) loglog(RC_LOG_SERIOUS, "\"%s\" line %d: %s", flp->filename, flp->lino, m); do {} while (shift()); return FALSE; } }