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