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