CCF Main Routine

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);
}