/* GNU m4 -- A simple macro processor Copyright (C) 1991, 1992, 1993, 1994 Free Software Foundation, Inc. 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "m4.h" #include #if __STDC__ #include #else #include #endif /* File for debugging output. */ FILE *debug = NULL; /* Obstack for trace messages. */ static struct obstack trace; extern int expansion_level; static void debug_set_file _((FILE *)); /*----------------------------------. | Initialise the debugging module. | `----------------------------------*/ void debug_init (void) { debug_set_file (stderr); obstack_init (&trace); } /*-----------------------------------------------------------------. | Function to decode the debugging flags OPTS. Used by main while | | processing option -d, and by the builtin debugmode (). | `-----------------------------------------------------------------*/ int debug_decode (const char *opts) { int level; if (opts == NULL || *opts == '\0') level = DEBUG_TRACE_DEFAULT; else { for (level = 0; *opts; opts++) { switch (*opts) { case 'a': level |= DEBUG_TRACE_ARGS; break; case 'e': level |= DEBUG_TRACE_EXPANSION; break; case 'q': level |= DEBUG_TRACE_QUOTE; break; case 't': level |= DEBUG_TRACE_ALL; break; case 'l': level |= DEBUG_TRACE_LINE; break; case 'f': level |= DEBUG_TRACE_FILE; break; case 'p': level |= DEBUG_TRACE_PATH; break; case 'c': level |= DEBUG_TRACE_CALL; break; case 'i': level |= DEBUG_TRACE_INPUT; break; case 'x': level |= DEBUG_TRACE_CALLID; break; case 'V': level |= DEBUG_TRACE_VERBOSE; break; default: return -1; } } } /* This is to avoid screwing up the trace output due to changes in the debug_level. */ obstack_free (&trace, obstack_finish (&trace)); return level; } /*------------------------------------------------------------------------. | Change the debug output stream to FP. If the underlying file is the | | same as stdout, use stdout instead so that debug messages appear in the | | correct relative position. | `------------------------------------------------------------------------*/ static void debug_set_file (FILE *fp) { struct stat stdout_stat, debug_stat; if (debug != NULL && debug != stderr && debug != stdout) fclose (debug); debug = fp; if (debug != NULL && debug != stdout) { if (fstat (fileno (stdout), &stdout_stat) < 0) return; if (fstat (fileno (debug), &debug_stat) < 0) return; if (stdout_stat.st_ino == debug_stat.st_ino && stdout_stat.st_dev == debug_stat.st_dev) { if (debug != stderr) fclose (debug); debug = stdout; } } } /*-----------------------------------------------------------. | Serialize files. Used before executing a system command. | `-----------------------------------------------------------*/ void debug_flush_files (void) { fflush (stdout); fflush (stderr); if (debug != NULL && debug != stdout && debug != stderr) fflush (debug); } /*-------------------------------------------------------------------------. | Change the debug output to file NAME. If NAME is NULL, debug output is | | reverted to stderr, and if empty debug output is discarded. Return TRUE | | iff the output stream was changed. | `-------------------------------------------------------------------------*/ boolean debug_set_output (const char *name) { FILE *fp; if (name == NULL) debug_set_file (stderr); else if (*name == '\0') debug_set_file (NULL); else { fp = fopen (name, "a"); if (fp == NULL) return FALSE; debug_set_file (fp); } return TRUE; } /*-----------------------------------------------------------------------. | Print the header of a one-line debug message, starting by "m4 debug". | `-----------------------------------------------------------------------*/ void debug_message_prefix (void) { fprintf (debug, "m4 debug: "); if (debug_level & DEBUG_TRACE_FILE) fprintf (debug, "%s: ", current_file); if (debug_level & DEBUG_TRACE_LINE) fprintf (debug, "%d: ", current_line); } /* The rest of this file contains the functions for macro tracing output. All tracing output for a macro call is collected on an obstack TRACE, and printed whenever the line is complete. This prevents tracing output from interfering with other debug messages generated by the various builtins. */ /*---------------------------------------------------------------------. | Tracing output is formatted here, by a simplified printf-to-obstack | | function trace_format (). Understands only %S, %s, %d, %l (optional | | left quote) and %r (optional right quote). | `---------------------------------------------------------------------*/ #if __STDC__ static void trace_format (const char *fmt, ...) #else static void trace_format (...) #endif { #if ! __STDC__ const char *fmt; #endif va_list args; char ch; int d; char nbuf[32]; const char *s; int slen; int maxlen; #if __STDC__ va_start (args, fmt); #else va_start (args); fmt = va_arg (args, const char *); #endif while (TRUE) { while ((ch = *fmt++) != '\0' && ch != '%') obstack_1grow (&trace, ch); if (ch == '\0') break; maxlen = 0; switch (*fmt++) { case 'S': maxlen = max_debug_argument_length; /* fall through */ case 's': s = va_arg (args, const char *); break; case 'l': s = (debug_level & DEBUG_TRACE_QUOTE) ? lquote.string : ""; break; case 'r': s = (debug_level & DEBUG_TRACE_QUOTE) ? rquote.string : ""; break; case 'd': d = va_arg (args, int); sprintf (nbuf, "%d", d); s = nbuf; break; default: s = ""; break; } slen = strlen (s); if (maxlen == 0 || maxlen > slen) obstack_grow (&trace, s, slen); else { obstack_grow (&trace, s, maxlen); obstack_grow (&trace, "...", 3); } } va_end (args); } /*------------------------------------------------------------------. | Format the standard header attached to all tracing output lines. | `------------------------------------------------------------------*/ static void trace_header (int id) { trace_format ("m4trace:"); if (debug_level & DEBUG_TRACE_FILE) trace_format ("%s:", current_file); if (debug_level & DEBUG_TRACE_LINE) trace_format ("%d:", current_line); trace_format (" -%d- ", expansion_level); if (debug_level & DEBUG_TRACE_CALLID) trace_format ("id %d: ", id); } /*----------------------------------------------------. | Print current tracing line, and clear the obstack. | `----------------------------------------------------*/ static void trace_flush (void) { char *line; obstack_1grow (&trace, '\0'); line = obstack_finish (&trace); DEBUG_PRINT1 ("%s\n", line); obstack_free (&trace, line); } /*-------------------------------------------------------------. | Do pre-argument-collction tracing for macro NAME. Used from | | expand_macro (). | `-------------------------------------------------------------*/ void trace_prepre (const char *name, int id) { trace_header (id); trace_format ("%s ...", name); trace_flush (); } /*-----------------------------------------------------------------------. | Format the parts of a trace line, that can be made before the macro is | | actually expanded. Used from expand_macro (). | `-----------------------------------------------------------------------*/ void trace_pre (const char *name, int id, int argc, token_data **argv) { int i; const builtin *bp; trace_header (id); trace_format ("%s", name); if (argc > 1 && (debug_level & DEBUG_TRACE_ARGS)) { trace_format ("("); for (i = 1; i < argc; i++) { if (i != 1) trace_format (", "); switch (TOKEN_DATA_TYPE (argv[i])) { case TOKEN_TEXT: trace_format ("%l%S%r", TOKEN_DATA_TEXT (argv[i])); break; case TOKEN_FUNC: bp = find_builtin_by_addr (TOKEN_DATA_FUNC (argv[i])); if (bp == NULL) { M4ERROR ((warning_status, 0, "\ INTERNAL ERROR: Builtin not found in builtin table! (trace_pre ())")); abort (); } trace_format ("<%s>", bp->name); break; default: M4ERROR ((warning_status, 0, "INTERNAL ERROR: Bad token data type (trace_pre ())")); abort (); } } trace_format (")"); } if (debug_level & DEBUG_TRACE_CALL) { trace_format (" -> ???"); trace_flush (); } } /*-------------------------------------------------------------------. | Format the final part of a trace line and print it all. Used from | | expand_macro (). | `-------------------------------------------------------------------*/ void trace_post (const char *name, int id, int argc, token_data **argv, const char *expanded) { if (debug_level & DEBUG_TRACE_CALL) { trace_header (id); trace_format ("%s%s", name, (argc > 1) ? "(...)" : ""); } if (expanded && (debug_level & DEBUG_TRACE_EXPANSION)) trace_format (" -> %l%S%r", expanded); trace_flush (); }