CCF Keyword Handling

The implementation is fairly straightforward. First we have a huge number of little routines, each of which handles one keyword, ...

#include	<ctype.h>
#include	<limits.h>
#include	<string.h>
#include	<time.h>

#include	"expr.h"
#include	"iofns.h"
#include	"keywords.h"
#include	"memfns.h"
#include	"syms.h"

if_element	now;
int		comment_override;
int		need_when_next;
long		next_enum = LONG_MIN;

#define	LOCAL_COMMENT	1
#define	INHERIT_COMMENT	2

static void do_if_branch (void)
{
  long expr;

  if (now.seen_active)
  {
    expr = 0;
    *scan_from = '\0';
  }
  else
    expr = Evaluate (1);

  switch (comment_override)
  {
  case ALL_COMMENTS:	now.commenting_out = 1;	break;
  case NO_COMMENTS:	now.commenting_out = 0;	break;
  default:
    if (expr > 0)
    {
      now.seen_active = line_number;
      now.commenting_out &= ~LOCAL_COMMENT;
    }
    else
      now.commenting_out |= LOCAL_COMMENT;
    break;
  }
}

static void push_if_element (int really_case)
{
  char *ccf_at = strstr (lc_line, lc_lead_in);
  size_t prefix = ccf_at - lc_line;
  if_element *p = my_malloc (sizeof (if_element));
  *p = now;
  now.seen_active = 0;
  if (now.commenting_out)
    now.commenting_out = INHERIT_COMMENT;
  now.seen_else = 0;
  now.opened_at = line_number;
  now.really_case = really_case;
  now.spaces = my_malloc (1 + prefix);
  if (prefix)
    memcpy (now.spaces, current_line, prefix);
  now.spaces [prefix] = '\0';
  now.prev = p;
}

static void do_if (void)
{
  push_if_element (0);
  do_if_branch ();
}

static void do_case (void)
{
  push_if_element (1);
  now.case_value = Evaluate (0);
  now.prev_when = 0;
  need_when_next = 2;
}

static void check_conditional (int fussy, int really_case)
{
  if (! now.prev)
    FatalError ("no conditional currently open");
  if (fussy && now.really_case != really_case)
    FatalError ("CASE/IF mis-match");
}

static void do_when (void)
{
  long val;
  need_when_next = 0;
  check_conditional (1, 1);
  val = Evaluate (0);

  if (val == now.case_value && now.seen_active)
    FatalPrintf (1, "Duplicate WHEN value (see line %ld)", now.seen_active);

  switch (comment_override)
  {
  case ALL_COMMENTS:	now.commenting_out = 1;	break;
  case NO_COMMENTS:	now.commenting_out = 0;	break;
  default:
    if (val == now.case_value)
    {
      now.seen_active = line_number;
      now.commenting_out &= ~LOCAL_COMMENT;
    }
    else if (now.prev_when != line_number - 1)
      now.commenting_out |= LOCAL_COMMENT;
    break;
  }
  now.prev_when = line_number;
}

static void check_else (void)
{
  check_conditional (0, 0);
  if (now.seen_else)
    FatalError ("else already seen for this conditional");
}


static void do_elseif (void)
{
  check_else ();
  do_if_branch ();
}


static void do_else (void)
{
  check_else ();
  if (! now.really_case && GetToken ())
  {
    if (strcmp (token, "if") == 0)
      do_elseif ();
    else
      FatalError ("unrecognized CCF line");
  }
  else
  {
    now.seen_else = 1;
    if (! comment_override)
    {
      if (now.seen_active)
	now.commenting_out |= LOCAL_COMMENT;
      else
      {
	now.commenting_out &= ~LOCAL_COMMENT;
	now.seen_active = line_number;
      }
    }
  }
}

static void end_conditional (int really_case)
{
  if_element *p = now.prev;
  check_conditional (1, really_case);
  my_free (now.spaces);
  now = *p;
  my_free (p);
}

static void do_endif (void)
{
  end_conditional (0);
}

static void do_endcase (void)
{
  end_conditional (1);
}

static void do_end (void)
{
  if (GetToken ())
  {
    if (strcmp (token, "if") == 0)
    {
      end_conditional (0);
      return;
    }
    if (strcmp (token, "case") == 0)
    {
      end_conditional (1);
      return;
    }
  }
  FatalError ("unrecognized CCF line");
}

static void do_set_or_default (int really_set)
{
  if (GetToken ())
  {
    char *name = my_malloc (1 + strlen (token));
    long v;
    strcpy (name, token);
    v = Evaluate (0);
    if (really_set || ! IsDefined (name))
      SetValue (name, v);

    my_free (name);
  }
  else
    FatalError ("missing name for assignment");
}

static void do_set (void)
{
  do_set_or_default (1);
}

static void do_default (void)
{
  do_set_or_default (0);
}

static int next_token (char *name, int *count)
{
  while (GetToken ())
  {
    if (! isdigit (*token) && *token != '-')
    {
      *count = 1;
      return (1);
    }
  }
  if (! *count)
    FatalPrintf (1, "missing name list for %s", name);
  return (0);
}

static void do_unset (void)
{
  int done_one = 0;

  while (next_token ("unset", &done_one))
  {
    if (! IsDefined (token))
      FatalPrintf (1, "'%.100s' is not set", token);
    Unset (token);
  }
}

static void do_unset_bang (void)
{
  int done_one = 0;

  while (next_token ("unset!", &done_one))
    Unset (token);
}

static void do_report (void)
{
  int done_one = 2;

  while (next_token ("report", &done_one))
    if (IsDefined (token))
      fprintf (stderr, "%s: %ld\n", token, GetValue (token));
    else
      fprintf (stderr, "%s: <undefined>\n", token);
  if (done_one == 2)
    ListSymbols ();
}

static void chatty_user (void (*out_fn) (char *msg))
{
  char *start = current_line + (scan_from - lc_line);
  char *end = start + strlen (scan_from);
  char was = *end;
  *scan_from = *end = '\0';
  while (*start && isspace (*start))
    start += 1;
  if (! comment_override)
    out_fn (start);
  *end = was;
}

static void do_error (void)
{
  chatty_user (FatalError);
}

static void do_warn (void)
{
  chatty_user (Warning);
}

static void do_say (void)
{
  chatty_user (JustSay);
}

static void do_date (void)
{
  if (output_enabled)
  {
    current_line [scan_from - lc_line] = '\0';
    if (! comment_override)
    {
      time_t x = time (NULL);
      char *p = ctime (&x);
      fputs (current_line, stdout);
      putchar (' ');
      *current_line = '\0';
      while (*p && *p != '\n')
	putchar (*p++);
    }
  }
  *scan_from = '\0';
  add_tail = 1;
}

static void do_settings (void)
{
  if (output_enabled)
  {
    int done_one = 0;
    current_line [scan_from - lc_line] = '\0';

    fputs (current_line, stdout);
    *current_line = '\0';

    while (next_token ("settings", &done_one))
      if (IsDefined (token))
	printf ("   %s %ld", token, GetValue (token));
      else
	printf ("   %s -", token);
  }
  add_tail = 1;
}

static void do_enum (void)
{
  int done_one = 0;

  while (next_token ("enum", &done_one))
  {
    if (IsDefined (token))
      FatalPrintf (1, "enum '%.100s' already has a value", token);
    SetValue (token, next_enum++);
  }
}


static void do_comment_marker (void)
{
  if (! GetToken () || *token >= 0x7f || strlen (token) != 1)
    FatalPrintf (1, "comment marker must be a single printable character");
  ccf_did_it = *token;
}

After the handlers have all been seen by the compiler, we collect them into an array. Then the process_keyword() function just slides down the list, checking its given token against the names and, if it finds a match, calling the given action. Some actions, of course, should be hidden away if they're inside an if, so the always and the commenting_out variables are tested before doing the call.


typedef struct
{
  char	 *name;
  int	  always;
  void	(*action) (void);
} action;


static action action_table [] =
{
  {"case",		1,	do_case},
  {"date",		0,	do_date},
  {"default",		0,	do_default},
  {"elif",		1,	do_elseif},
  {"else",		1,	do_else},
  {"else-if",		1,	do_elseif},
  {"elsif",		1,	do_elseif},
  {"end",		1,	do_end},
  {"end-case",		1,	do_endcase},
  {"end-if",		1,	do_endif},
  {"endcase",		1,	do_endcase},
  {"endif",		1,	do_endif},
  {"enum",		0,	do_enum},
  {"error",		0,	do_error},
  {"esac",		1,	do_endcase},
  {"fi",		1,	do_endif},
  {"hide",		0,	do_comment_marker},
  {"if",		1,	do_if},
  {"otherwise",		1,	do_else},
  {"report",		0,	do_report},
  {"say",		0,	do_say},
  {"set",		0,	do_set},
  {"settings",		0,	do_settings},
  {"unset",		0,	do_unset},
  {"unset!",		0,	do_unset_bang},
  {"unsetxx",		0,	do_unset_bang},
  {"warning",		0,	do_warn},
  {"when",		1,	do_when},

  {NULL,		0,	NULL}
};

void process_keyword (void)
{
  if (GetToken ())
  {
    action *cmd;
    for (cmd = action_table; cmd -> name; cmd += 1)
      if (strcmp (token, cmd -> name) == 0)
      {
	if (need_when_next && cmd -> action != do_when)
	  FatalError ("WHEN expected after CASE");

	if (cmd -> always || ! now.commenting_out)
	{
	  cmd -> action ();
	  if (GetToken ())
	    Warning ("junk at end of line");
	}
	break;
      }
    if (! cmd-> name)
      FatalError ("unrecognized CCF line");
  }
}


The only other thing to do here is complain if the user's fallen off the end of a source file with an if or case still open.

void check_balance (void)
{
  while (now.prev)
  {
    FatalPrintf (0, "end of file with conditional (line %ld) open",
		 now.opened_at);
    end_conditional (now.really_case);
  }
}