As well as doing the command-line parsing bit, this is also where the comment insertion and deletion gets done.
#include <ctype.h> #include <stdio.h> #include <string.h> #include "iofns.h" #include "keywords.h" #include "memfns.h" #include "syms.h" #define VERSION_NUMBER 1014 #define VERSION_STRING "1014a" int output_enabled; int add_tail; int ccf_did_it = '!'; char *lc_lead_in; static char *lead_in; static size_t ccf_len; static char *tail; static char *lc_tail; static size_t tail_len; static int nested; static int corrupt_blank_lines = 0; static void lose_string (char **x) { if (*x) { my_free (*x); *x = NULL; } }
Every source file gets its own comment convention, and hence its own lead_in string etc.
static void open_file (char *name) { lose_string (&lead_in); lose_string (&lc_lead_in); lose_string (&tail); lose_string (&lc_tail); ccf_len = tail_len = 0; now.commenting_out = 0; now.spaces = ""; line_number = 0; if (strcmp (name, "-") == 0) { filename = "<stdin>"; src_file = stdin; } else { filename = name; if ((src_file = fopen (name, "r")) == NULL) FatalError ("can't open file"); } } static void close_file (void) { if (src_file != stdin) fclose (src_file); src_file = NULL; }
This function, allowed_blank_line, returns true if (and only if) the line you give it is all whitespace and you're allowed to output blank lines unchanged
static int allowed_blank_line (char *p) { if (corrupt_blank_lines) return (0); while (*p) if (! isspace (*p)) return (0); else p += 1; return (1); }
Here's where we look for the initialisation line. The magic word is cunningly split into two strings so that, were anyone fool enough to run CCF on its own sources, nothing too nasty would happen.
Once we find the initialisation line we copy the head and tail portions into lead_in and tail, together with their lc_ counterparts.
static int find_init (void) { while (GetLine ()) { char *p = strstr (lc_line, "ccf:" "init"); if (output_enabled) puts (current_line); if (p) { char *pre = lc_line; char *post = p + 8; *p = '\0'; while (*pre && isspace (*pre)) pre += 1; p = pre + strlen (pre); while (p > pre && isspace (p [-1])) p -= 1; *p = '\0'; ccf_len = strlen (pre); if (ccf_len) { lead_in = my_malloc (ccf_len + 1); lc_lead_in = my_malloc (ccf_len + 1); strcpy (lc_lead_in, pre); memcpy (lead_in, current_line + (pre - lc_line), ccf_len); lead_in [ccf_len] = '\0'; while (*post && isspace (*post)) post += 1; p = post + strlen (post); while (p > post && isspace (p [-1])) p -= 1; *p = '\0'; if (strlen (post) > 6 && memcmp (post, "nested", 6) == 0) { nested = 1; post += 6; while (*post && isspace (*post)) post += 1; } tail_len = strlen (post); if (tail_len) { tail = my_malloc (tail_len + 1); lc_tail = my_malloc (tail_len + 1); strcpy (lc_tail, post); memcpy (tail, current_line + (post - lc_line), tail_len); tail [tail_len] = '\0'; } return (1); } } } return (0); } static void adjust_lc_line (void) { char *p = lc_line; char *q = current_line; while ((*p++ = tolower (*q++)) != '\0') ; }
The chop_tail() routine just removes any trailing comment markers and whitespace from the end of lc_line and, optionally, from current_line.
static int chop_tail (int and_current) { size_t len = strlen (lc_line); char *p = lc_line + len; int found_tail = !tail; while (len && p [-1] == ' ') { len -= 1; *--p = '\0'; } if (tail && len >= tail_len) { p -= tail_len; if (strcmp (p, lc_tail) == 0) { len -= tail_len; while (len && p [-1] == ' ') { len -= 1; p -= 1; } *p = '\0'; if (and_current) current_line [len] = '\0'; found_tail = 1; } } return (found_tail); }
Now here's a messy routine for you: decomment_it(). What we have to do is convert a commented-out line back into something usable, but (and here's the fun part) we have to be careful of embedded comments just in case the original source language doesn't know how to cope.
static void decomment_it (size_t leading_space, char *p) { /* line was commented out: diddle it back to life */ char *target = current_line + leading_space; size_t offset = ccf_len + 1; if (*p == ' ') { p += 1; offset += 1; } memmove (target, target + offset, strlen (p) + 1); if (tail) { adjust_lc_line (); chop_tail (1); if (! nested) { char *q = current_line; char *r; p = lc_line;
OK - here we go.
At any time
We look for a "comment end" (with optional spaces), followed by a CCF-commented-this-out indicator. If we find such, we replace it with the original "comment end" and repeat.
while ((r = strstr (p, lc_tail)) != NULL) { r += tail_len; q += (r - p); p = r; while (isspace (*r)) r += 1; if (strlen (r) > ccf_len && memcmp (r, lc_lead_in, ccf_len) == 0 && r [ccf_len] == ccf_did_it ) { r += ccf_len + 1; if (*r == ' ') r += 1; memmove (q, q + (r - p), strlen (r) + 1); p = r; } else p += 1; } } } }
Here's put_body() which does the converse of the above comment jiggery-pokery. It's a lot easier, mainly because there's less checking to be done. If we find a non-nestable comment end marker, we follow it with space, "CCF comment start", "!"
static char *put_body (char *p) { if (tail && ! nested) { char *q, *r; adjust_lc_line (); q = lc_line + (p - current_line); while ((r = strstr (q, lc_tail)) != NULL) { for (r += tail_len; q < r; q += 1, p += 1) putchar (*p); putchar (' '); fputs (lead_in, stdout); putchar (ccf_did_it); putchar (' '); } } return (p); }
This is the one that processes whole lines and decides what's what. Anything starting with a CCF marker is passed to either decomment_it() or process_keyword(). Anything else just gets output (perhaps as a comment).
static void process_line (void) { char *p = lc_line; int special = 0; add_tail = 0; while (*p && isspace (*p)) p += 1; if (memcmp (p, lc_lead_in, ccf_len) == 0) { size_t leading_space = p - lc_line; p += ccf_len; if (*p == ccf_did_it) decomment_it (leading_space, p + 1); else if (chop_tail (0)) { special = 1; scan_from = p; if ((p = strstr (scan_from, lc_lead_in)) != NULL) { /* lose any trailing CCF comment ... */ *p = '\0'; /* ... whitespace ... */ while (p > scan_from && isspace (p [-1])) *--p = '\0'; /* ... and close-comment marker */ if (lc_tail && (p - scan_from) > tail_len && strcmp (p -= tail_len, lc_tail) == 0) *p = '\0'; } process_keyword (); } } switch (need_when_next) { case 1: FatalError ("WHEN expected after CASE"); break; case 2: need_when_next = 1; break; } if (output_enabled) { if (special || allowed_blank_line (current_line)) fputs (current_line, stdout); else { p = current_line; if (now.commenting_out) { char *spc = now.spaces; while (*p && *p == *spc) { putchar (*p); p += 1; spc += 1; } fputs (lead_in, stdout); putchar (ccf_did_it); putchar (' '); p = put_body (p); add_tail = 1; } fputs (p, stdout); } if (tail && add_tail) { putchar (' '); puts (tail); } else putchar ('\n'); } }
Tell the user who we are, and how hard it is to make pirate copies
static char *copying[] = { "\"Look out! He's got a gnu!\"\n", "CCF: a Conditional Compilation Facility for arbitrary source files.", "You are running version " VERSION_STRING ", copyright 1996-2000 Ian Wild", "(except for the quote above that I stole from Terry Pratchett).\n", "Great though it is, CCF comes with ABSOLUTELY NO WARRANTY.", "This is free software, and you are welcome to redistribute it", "under certain conditions; see the file COPYING.txt for details.", "Please report any bugs to the author at <imw@acm.org>.", NULL }; static void lack_of_warranty (void) { char **p = copying; while (*p) { puts (*p); p += 1; } }
So here's the main routine. Pretty much devoid of trickery - a loop that processes lines nested within a loop that opens files.
int main (int argc, char **argv) { int i; SetValue ("ccf:version", VERSION_NUMBER); #if defined(__MSDOS__) SetValue ("msdos", 1); #elif defined(__unix) SetValue ("unix", 1); #elif defined (__WIN32__) SetValue ("win32", 1); #else #error No operating system defined #endif for (i = 2; i <= argc; i += 1) { char *arg = argv [i - 1]; if (strcmp (arg, "-a") == 0) comment_override = ALL_COMMENTS; else if (strcmp (arg, "-b") == 0) corrupt_blank_lines = 1; else if (strcmp (arg, "-B") == 0) corrupt_blank_lines = 0; else if (strcmp (arg, "-n") == 0) comment_override = NO_COMMENTS; else if (strcmp (arg, "-s") == 0) comment_override = 0; else if (strcmp (arg, "-v") == 0) fputs ("CCF(binary) version " VERSION_STRING "\n", stderr); else if (strcmp (arg, "-w") == 0) lack_of_warranty (); else break; } if (i > argc) FatalError ("Usage: CCFbin [-abBnsvw] infile ... >outfile"); SetValue ("ccf:header", 1); SetValue ("ccf:mode", comment_override); for (; i <= argc; i += 1) { if (i == argc) { output_enabled = 1; SetValue ("ccf:header", 0); } open_file (argv [i - 1]); if (find_init ()) { while (GetLine ()) process_line (); check_balance (); } close_file (); } return (had_warning); }