/* * ChkTeX, utility functions. * Copyright (C) 1995-96 Jens T. Berger Thielemann * * 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 the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Contact the author at: * Jens Berger * Spektrumvn. 4 * N-0666 Oslo * Norway * E-mail: * * */ #include "ChkTeX.h" #include "Utility.h" #include "Resource.h" #include "OpSys.h" typedef unsigned long HASH_TYPE; /***************************** SUPPORT FUNCTIONS ************************/ /* * Copies a string with a maximum length of `len' starting at `pos' in * `source' into `dest'. * Returns -1 if the pos value is beyond the length of the source value, * else NULL. */ short substring(const char *source, char *dest, unsigned long pos, long len) { const char *Start; short Retval = -1; if (len >= 0) { if (strlen(source) > pos) { Start = &source[pos]; while ((len-- > 0) && (*dest++ = *Start++)) ; if (len == -1) Retval = 0; } } else Retval = 0L; *dest = 0; return (Retval); } /* * Determine whether a file exists. * */ int fexists(const char *Filename) { int Retval; #if defined(F_OK) && defined(R_OK) && defined(HAVE_ACCESS) Retval = access(Filename, F_OK | R_OK) == 0; #else FILE *fh; if ((fh = fopen(Filename, "r"))) { Retval = TRUE; fclose(fh); } else Retval = FALSE; #endif /* Ensure that it's not a directory */ if (Retval && !IsRegFile(Filename)) { Retval = FALSE; } return (Retval); } /* * 'Safe' memset() replacement. * Just tries to check the parameters, so that the risk of killing * innocent memory is lowered. * Please note that the `n' parameter now is an signed longword, not * an size_t. Won't permit that `n' exceeds BUFLEN, nor negative sizes. * Returns `to' if some memset()'ing was done, NULL if not. */ void *sfmemset(void *to, int c, long n) { if (to && (n > 0)) { n = min(n, BUFFER_SIZE); return (memset(to, c, (size_t) n)); } return (NULL); } /* * Quick replace function * Replaces every occurrence of a character in a string with another one. */ void strrep(char *String, /* String to replace within. */ const char From, /* Character to search for. */ const char To) /* Character to put instead. */ { register int c; while ((c = *String++)) { if (c == From) String[-1] = To; } } /* * Replaces every char not in Prot with To in Buf */ void strxrep(char *Buf, const char *Prot, const char To) { int c; while ((c = *Buf)) { if (!strchr(Prot, c)) *Buf = To; Buf++; } } /* * Strips tail and/or front of a string * Kills trailing/leading spaces. The macro/function LATEX_SPACE(char c) * is used to decide whether a space should be skipped. This function * should return TRUE if the character should be skipped, FALSE if not. * Returns the string which was passed onto it. */ char *strip(char *str, /* String to strip */ const enum Strip flags) /* One of the following: */ /* STRP_LFT - Strips leading blanks */ /* STRP_RGT - Strips trailing blanks */ /* STRP_BTH - Strips on both sides */ { char *bufptr = str; char *nlptr; char c; if (bufptr && (c = *bufptr)) { if (flags & STRP_LFT) { if (LATEX_SPACE(c) && c) { do { c = *++bufptr; } while (LATEX_SPACE(c) && c); } } if (flags & STRP_RGT) { if (c && *bufptr) { nlptr = bufptr; while (*++nlptr) ; do { *nlptr = 0; c = *--nlptr; } while (LATEX_SPACE(c) && c && (nlptr > bufptr)); } else *bufptr = 0; } } return (bufptr); } /* * Converts all the chars in the string passed into lowercase. */ #ifndef HAVE_STRLWR char *strlwr(char *String) { char *Bufptr; char TmpC; for (Bufptr = String; (TmpC = *Bufptr); Bufptr++) *Bufptr = tolower((unsigned char)TmpC); return (String); } #endif /* * Returns a duplicate of the string passed. */ #ifndef HAVE_STRDUP char *strdup(const char *String) { char *Retval = NULL; size_t Len; if (String) { Len = strlen(String) + 1; if ((Retval = malloc(Len))) memcpy(Retval, String, Len); } return (Retval); } #endif /* * Does the same as strdup, but adds a zero-filled padding, length extra bytes. */ char *strdupx(const char *String, int Extra) { char *Retval = NULL; size_t Len; if (String) { Len = strlen(String) + 1 + Extra; if ((Retval = malloc(Len))) strncpy(Retval, String, Len); } return (Retval); } /* * Case-insensitive comparison of two strings. */ #ifndef HAVE_STRCASECMP int strcasecmp(const char *a, const char *b) { int aa, bb; do { aa = *a++; bb = *b++; } while (aa && (tolower((unsigned char)aa) == tolower((unsigned char)bb))); /* bb != 0 is implicit */ return (tolower((unsigned char)aa) - tolower((unsigned char)bb)); } #endif /* * Not all reallocs are intelligent enough to handle NULL's as * parameters. This fixes this. */ void *saferealloc(void *b, size_t n) { void *Retval = NULL; if (b) { if (n) Retval = realloc(b, n); else free(b); } else Retval = malloc(n); return (Retval); } /* * Repeatedly writes the From string over To so that we overwrite Len bytes. * Does nothing if passed empty/NULL string. */ void strwrite(char *To, const char *From, unsigned long Len) { unsigned long i, j; unsigned long FromLen = strlen(From); Len = min(Len, BUFFER_SIZE); if (To && From) { switch (FromLen) { case 0: break; case 1: memset(To, *From, Len); break; default: for (i = j = 0; i < Len; i++, j++) { if (j >= FromLen) j = 0; To[i] = From[j]; } } } } /* * Checks whether Cmp comes after Str. * */ int strafter(const char *Str, const char *Cmp) { return (strncmp(Str, Cmp, strlen(Cmp))); } /* * Checks whether Cmp comes before Str. Returns 0 if so, non-zero if not. * */ int strinfront(const char *Str, const char *Cmp) { int CmpLen; if ((CmpLen = strlen(Cmp))) { Cmp += CmpLen; Str++; while ((*--Cmp == *--Str) && (--CmpLen > 0)) ; return (CmpLen); } else return (1); } /*************************** HASH INDEX **************************/ /* * Hashes a string. The string ought be rather short. * * The algorithm was designed by Peter Weinberger. This version was * adapted from Dr Dobb's Journal April 1996 page 26. */ static unsigned long HashWord(const char *str) { register unsigned long h = 0, hbit, c; while ((c = *str++)) { h = (h << 4) ^ c; if ((hbit = h & 0xf0000000)) h ^= hbit >> 24; h &= ~hbit; } return (h); } /* * Inserts a string into a hash index. Note: You'll have to * duplicate the string yourself. */ void InsertHash(char *a, struct Hash *h) { struct HashEntry **he, *newhe; if (!h->Index) { if (!((h->Index = calloc(HASH_SIZE, sizeof(struct HashEntry *))))) PrintPrgErr(pmWordListErr); } he = &h->Index[HashWord(a) % HASH_SIZE]; if ((newhe = malloc(sizeof(struct HashEntry)))) { newhe->Next = *he; newhe->Str = a; *he = newhe; } else PrintPrgErr(pmWordListErr); } /* * Checks whether a string previously has been registered in a * hash index. */ char *HasHash(const char *a, const struct Hash *h) { struct HashEntry *he; HASH_TYPE i; /* Special magic to optimize SAS/C */ /* Do we have a hash? */ if (!h->Index) return NULL; /* Find entry in hash */ i = HashWord(a); i %= HASH_SIZE; he = h->Index[i]; /* Search in the entry for the item */ while (he) { if (!strcmp(he->Str, a)) return (he->Str); else he = he->Next; } return (NULL); } /* * Clears a hash table. */ void ClearHash(struct Hash *h) { int i; struct HashEntry *he, *next; if (h && h->Index) { /* Free all the memory */ for ( i = 0; i < HASH_SIZE; ++i ) { he = h->Index[i]; while ( he ) { next = he->Next; free( he ); he = next; } } /* Reset the hash table */ memset(h->Index, '\0', HASH_SIZE * sizeof(struct HashEntry *)); } } /* * Rehashes a wordlist. If you change any of the elem's, you must * call this. * */ static void ReHash(struct WordList *WL) { unsigned long i = 0; ClearHash(&WL->Hash); FORWL(i, *WL) InsertHash(WL->Stack.Data[i], &WL->Hash); } /*************************** WORDLIST HANDLING **************************/ /* * Inserts a duplicate of `Word' into the `Wordlist' structure. You do thus * not need to make a duplicate of `Word' yourself. */ int InsertWord(const char *Word, struct WordList *WL) { char *WrdCpy; unsigned long Len; if ((WrdCpy = strdupx(Word, WALLBYTES))) { if (StkPush(WrdCpy, &WL->Stack)) { Len = strlen(Word); if (WL->MaxLen < Len) WL->MaxLen = Len; InsertHash(WrdCpy, &WL->Hash); return (TRUE); } free(WrdCpy); } return (FALSE); } /* * Clears a WordList; removing all items. */ void ClearWord(struct WordList *WL) { char *Word; if (WL) { while ( (Word = StkPop( &WL->Stack )) ) { free(Word); } WL->Stack.Used = 0; WL->MaxLen = 0; ClearHash(&WL->Hash); if (WL->NonEmpty) InsertWord("", WL); } } /* * Query whether a `Word' is previously InsertWord()'ed into the WL * structure. Does case-sensitive comparison. * * Returns the data in the list. */ char *HasWord(const char *Word, struct WordList *WL) { return HasHash(Word, &WL->Hash); } /* * Make all the words in a list lower case for later case-insensitive * comparison. */ void MakeLower(struct WordList *wl) { unsigned long i; FORWL(i, *wl) strlwr(wl->Stack.Data[i]); ReHash(wl); } /* * Calls strrep on each argument in a list. */ void ListRep(struct WordList *wl, const char From, const char To) { unsigned long i; FORWL(i, *wl) strrep(wl->Stack.Data[i], From, To); ReHash(wl); } /************************** GENERIC STACK ******************************/ /* * Push something onto a stack. Returns TRUE if successful, else FALSE. * Note: You can not push a NULL Data element. */ int StkPush(void *Data, struct Stack *Stack) { unsigned long NewSize; void **NewBuf; if (Data && Stack) { if (Stack->Used >= Stack->Size) { NewSize = Stack->Size + MINPUDDLE; if ((NewBuf = saferealloc(Stack->Data, (size_t) NewSize * sizeof(void *)))) { Stack->Size = NewSize; Stack->Data = NewBuf; } else return (FALSE); } Stack->Data[Stack->Used++] = Data; return (TRUE); } return (FALSE); } /* * Pops an element from the stack. * */ void *StkPop(struct Stack *Stack) { void *Retval = NULL; if (Stack && (Stack->Used > 0)) { Retval = Stack->Data[--Stack->Used]; #ifdef NO_DIRTY_TRICKS { void **NewBuf; if (Stack->Used < (Stack->Size / 2)) { unsigned long NewSize; NewSize = Stack->Size - MINPUDDLE; NewSize = max(NewSize, MINPUDDLE); if (NewBuf = saferealloc(Stack->Data, (size_t) NewSize * sizeof(void *))) { Stack->Size = NewSize; Stack->Data = NewBuf; } } } #endif } return (Retval); } /* * Returns the topmost element of the stack. */ void *StkTop(struct Stack *Stack) { if (Stack && (Stack->Used > 0)) return (Stack->Data[Stack->Used - 1]); else return (NULL); } /****************************** INPUT STACK *****************************/ int PushFileName(const char *Name, struct Stack *stack) { FILE *fh = NULL; static char NameBuf[BUFFER_SIZE]; if (Name && stack) { if (LocateFile(Name, NameBuf, ".tex", &TeXInputs)) { if ((fh = fopen(NameBuf, "r"))) { return (PushFile(NameBuf, fh, stack)); } } PrintPrgErr(pmNoTeXOpen, Name); } return (FALSE); } int PushFile(const char *Name, FILE * fh, struct Stack *stack) { struct FileNode *fn; uint64_t *filesupp; if (Name && fh && stack) { if ((fn = malloc(sizeof(struct FileNode)))) { if ((fn->Name = strdup(Name))) { fn->fh = fh; fn->Line = 0L; if ((filesupp = malloc(sizeof(uint64_t)))) { *filesupp = 0; StkPush(filesupp, &FileSuppStack); } else PrintPrgErr(pmNoStackMem); if ((filesupp = malloc(sizeof(uint64_t)))) { *filesupp = 0; StkPush(filesupp, &UserFileSuppStack); } else PrintPrgErr(pmNoStackMem); if (StkPush(fn, stack)) return (TRUE); free(fn->Name); } free(fn); } PrintPrgErr(pmNoStackMem); } return (FALSE); } char *FGetsStk(char *Dest, unsigned long len, struct Stack *stack) { static short HasSeenLong = 0; struct FileNode *fn; char *Retval = NULL; size_t Retlen = 0; if ((fn = StkTop(stack))) { do { Retval = fgets(Dest, (int)len, fn->fh); if (Retval) { Retlen = strlen(Retval); if (Retval[Retlen-1] == '\n' || Retlen < len-1) fn->Line++; /* We only want the long lines warning once per file */ else if (!HasSeenLong) { PrintPrgErr(pmLongLines, len-2); HasSeenLong = 1; } break; } fn = StkPop(stack); fclose(fn->fh); if ( fn->Name != NULL ) free(fn->Name); free(fn); HasSeenLong = 0; StkPop(&FileSuppStack); StkPop(&UserFileSuppStack); } while (!Retval && (fn = StkTop(stack))); } return (Retval); } const char *CurStkName(struct Stack *stack) { struct FileNode *fn; static const char *LastName = ""; if (PseudoInName && (stack->Used <= 1)) return (PseudoInName); else { if ((fn = StkTop(stack))) { if ( stack->Used == 1 && strlen(LastName) == 0 && fn->Name ) { LastName = strdup(fn->Name); } return (fn->Name); } else return (LastName); } } FILE *CurStkFile(struct Stack * stack) { struct FileNode *fn; if ((fn = StkTop(stack))) return (fn->fh); else return (NULL); } unsigned long CurStkLine(struct Stack *stack) { struct FileNode *fn; static unsigned long LastLine = 0L; if ((fn = StkTop(stack))) return (LastLine = fn->Line); else return (LastLine); } long CurStkMode(struct Stack *stack) { long * Mode; if ((Mode = StkTop(stack))) return *Mode; /* printf("Empty stack\n"); */ return FALSE; } long *PushMode(long mode, struct Stack *stack) { long *m; if ((m = malloc(sizeof(long)))) { *m = mode; StkPush(m, stack); return m; } return NULL; } /************************** CHARACTER STACK ******************************/ /* * Pushes the character on the stack. */ struct ErrInfo *PushChar(const char c, const unsigned long Line, const unsigned long Column, struct Stack *Stk, const char *LineCpy) { char Buf[2]; Buf[0] = c; Buf[1] = 0; return (PushErr(Buf, Line, Column, 1, LineCpy, Stk)); } struct ErrInfo *PushErr(const char *Data, const unsigned long Line, const unsigned long Column, const unsigned long ErrLen, const char *LineCpy, struct Stack *Stk) { struct ErrInfo *ci; if ((ci = malloc(sizeof(struct ErrInfo)))) { if ((ci->Data = strdup(Data))) { if ((ci->File = strdup(CurStkName(&InputStack)))) { if ((ci->LineBuf = strdup(LineCpy))) { ci->Line = Line; ci->ErrLen = ErrLen; ci->Column = Column; ci->Flags = efNone; if (StkPush(ci, Stk)) return (ci); free(ci->LineBuf); } else PrintPrgErr(pmStrDupErr); free(ci->File); } else PrintPrgErr(pmStrDupErr); free(ci->Data); } else PrintPrgErr(pmStrDupErr); free(ci); } return (NULL); } /* * Finds the uppermost entry in the stack with a data matching * String. */ struct ErrInfo *TopMatch(struct Stack *Stack, char *String) { int i; struct ErrInfo *retval = NULL; if (Stack && String) { for (i = Stack->Used - 1; i >= 0; i--) { if (!strcmp(String, ((struct ErrInfo *) Stack->Data[i])->Data)) { retval = (struct ErrInfo *) Stack->Data[i]; break; } } } return (retval); } /* * Returns and removes a character from the stack, returns NULL if * the stack is empty. */ struct ErrInfo *PopErr(struct Stack *Stack) { return ((struct ErrInfo *) StkPop(Stack)); } /* * Same as PopChar(), but lets the error alone on the stack. */ struct ErrInfo *TopErr(struct Stack *Stack) { return ((struct ErrInfo *) StkTop(Stack)); } /* * Free all resources associated with a struct FreeInfo. */ void FreeErrInfo(struct ErrInfo *ei) { if (ei) { if (ei->Data) free(ei->Data); if (ei->File) free(ei->File); if (ei->LineBuf) free(ei->LineBuf); free(ei); } } /************************* OPEN/CLOSE COUNTING **************************/ /* * Returns the index a given bracket (`()[]{}') character has in the * BrOrder array. Returns ~0 if the character was not a bracket. */ long BrackIndex(const char c) { switch (c) { case '(': return (0); case ')': return (1); case '[': return (2); case ']': return (3); case '{': return (4); case '}': return (5); default: return (~0L); } } /* * Counts brackets for you. Give it a bracket, and it will update the * corresponding counter. */ void AddBracket(const char c) { long Index; if ((Index = BrackIndex(c)) != -1) Brackets[Index]++; } /* * Returns the character that matches the given bracket, NULL if `c' * wasn't a bracket character. */ char MatchBracket(const char c) { unsigned long Index; char Char = 0; if ((Index = BrackIndex(c)) != ~0UL) Char = BrOrder[Index ^ 1]; return (Char); }