BACK to addon.html#char-mapper

 
 /**
  * \file char-mapper.c
  *
  *  This is the main routine for char-mapper.
  *
  *  This file is part of char-mapper.
  *  char-mapper Copyright (C) 2003-2013 by Bruce Korb - all rights reserved
  *
  * char-mapper is free software: you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
  * Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
  *
  * char-mapper is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  * See the GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License along
  * with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 #include "char-mapper.h"
 
 int
 main(int argc, char ** argv)
 {
     program = strrchr(argv[0], '/');
     if (program++ == NULL)
         program = argv[0];
 
     if (argc > 1) {
         char * pz = argv[1];
         if (*pz == '-')
             parse_help(pz);
         if (freopen(argv[1], "r", stdin) != stdin) {
             fprintf(stderr, "fs error %d (%s) reopening '%s' as stdin\n",
                     errno, strerror(errno), pz);
             exit(EXIT_FAILURE);
         }
 
     } else if (isatty(STDIN_FILENO))
         parse_help(NULL);
 
     else argv[1] = "stdin";
 
     bit_count = read_data();
     init_names();
 
     emit_leader(argv[1]);
     emit_macros(bit_count);
     emit_table(bit_count);
     if (optimize_code)
         emit_opt_functions();
     else
         emit_functions();
     return EXIT_SUCCESS;
 }
 
 void
 die(char const * fmt, ...)
 {
     va_list ap;
     fputs("char-mapper error:  ", stderr);
     va_start(ap, fmt);
     vfprintf(stderr, fmt, ap);
     va_end(ap);
     exit(EXIT_FAILURE);
 }
 
 static void
 emit_leader(char * input)
 {
     if (add_test_code) {
         if (out_file_nm != NULL)
             printf(test_script_fmt, base_fn_nm, base_ucase, out_file_nm);
     }
 
     {
         time_t tm = time(NULL);
         struct tm * tmp = localtime(&tm);
         char const * mask_type =
             (bit_count <= 8) ? type_8bits
             : ((bit_count <= 16) ? type_16bits
                : ((bit_count <= 32) ? type_32bits
                   : type_64bits ));
 
         strftime(buffer, BUF_SIZE, "%x at %X", tmp);
         printf(leader_fmt, bit_count, total_map_ct, buffer,
                commentary, file_guard, mask_type, program);
     }
 
     if (fseek(stdin, 0, SEEK_SET) == 0)
         copy_input_text(input);
 
     {
         int width = (bit_count+3)/4;
         if (width < 2) width = 2;
         sprintf(mask_fmt, mask_fmt_fmt, width);
         if (width > 8)
             strcat(mask_fmt, "ULL");
     }
 }
 
 static void
 init_names(void)
 {
     if (table_name == NULL) {
 
         /*
          * If the guard is unchanged from the initial default,
          */
         if (file_guard == char_map_gd)
             table_name = "char_type_table";
 
         else {
             static char const grd[] = "_GUARD";
             char * p = NULL;
             char const * e = file_guard + strlen(file_guard)
                 - (sizeof(grd) - 1);
 
             /*
              * If the guard ends with "_GUARD", then set "e" back to the end.
              * Otherwise, if it ends with "_H_GUARD", then trim the "_H" too.
              */
             if (strcmp(e, grd) != 0)
                 e += sizeof(grd) - 1;
             else if ((e[-2] == '_') && (e[-1] == 'H'))
                 e -= 2;
 
             /*
              * Make the table name be the downcased, trimmed guard.
              */
             size_t copy_len = e - file_guard;
             table_name = p = malloc(copy_len + table_sfx_LEN + 1);
             memcpy(p, file_guard, copy_len);
             memcpy(p + copy_len, table_sfx, table_sfx_LEN + 1);
             for (; p < table_name + copy_len; p++)
                 *p = tolower((int)*p);
         }
     }
 
     /*
      *  If "%guard" has not been specified (as either empty or with a name),
      *  then create an ifdef name to guard the data tables.
      */
     if (! table_is_static && (data_guard == NULL)) {
         char * p = malloc(strlen(table_name) + define_fmt_LEN);
         sprintf(p, define_fmt, table_name);
         make_define_name(p);
         data_guard = p;
     }
 
     {
         static char const msk[] = "_mask_t";
         size_t base_name_len = strlen(table_name); ///< base name length
 
         /*
          * If the table name already has a "_table" suffix, don't count it in
          * the base name length.  Otherwise, "table_name" is our base name.
          */
         if (strcmp(table_name + base_name_len - table_sfx_LEN, table_sfx) == 0)
             base_name_len -= table_sfx_LEN;
 
         char * p = malloc(base_name_len + table_sfx_LEN + 1);
         memcpy(p, table_name, base_name_len);
         memcpy(p + base_name_len, msk, sizeof(msk));
         mask_name = p;
 
         p = malloc(base_name_len + 1);
         memcpy(p, table_name, base_name_len);
         p[base_name_len]  = NUL;
         base_fn_nm = p; // base name of most everything
 
         p = malloc(base_name_len + 1);
         memcpy(p, table_name, base_name_len);
         p[base_name_len]  = NUL;
         make_define_name(p);
         base_ucase = p; // upper case flavor of our base name
     }
 }
 
 /**
  * Copy out the input to the output as a comment.
  * The input file was successfully rewound, so we simply re-read it.
  *
  * @param name the name of the input file.
  */
 static void
 copy_input_text(char const * name)
 {
     bool skip_comment = false;
 
     printf(copy_input_start, name);
     while (fgets(buffer, BUF_SIZE, stdin) != NULL) {
         if (strstr(buffer, "%comment") != NULL) {
             skip_comment = true;
             fputs("// %comment -- see above\n", stdout);
             continue;
         }
         if (strstr(buffer, "%emit") != NULL) {
             skip_comment = true;
             fputs("// %emit code -- see below\n", stdout);
             continue;
         }
         if (skip_comment) {
             int bfix = 0;
             while (isspace(buffer[bfix]))  bfix++;
             if (buffer[bfix] != '%') continue;
             skip_comment = false;
         }
         printf("// %s", buffer);
     }
 
     fwrite(copy_input_end, copy_input_end_LEN, 1, stdout);
 }
 
 /**
  * See of the command argument matches a help request.
  * We accept "-h" and "--h[e[l[p]]]".
  *
  * @param name the name of the input file.
  */
 void
 parse_help(char const * opt_pz)
 {
     int exit_code = EXIT_SUCCESS;
     FILE * fp = stdout;
     char const * pz = opt_pz;
 
     if (opt_pz == NULL) {
         fp = stderr;
         fprintf(stderr, opterrmsg, input_is_tty, must_be_file);
         fwrite(usage_text, usage_text_LEN, 1, fp);
         exit_code = EXIT_FAILURE;
     } else
 
     switch (pz[1]) {
     case '-':
         pz += 2;
         static char const help[] = "help";
         char const * p = help;
         for (;;) {
             if (*pz != *p)
                 goto bad_opt;
             if (*(p++) == NUL)
                 break;
             if (*++pz == NUL)
                 break;
         }
         /* FALLTHROUGH */
 
     case 'h':
         fwrite(usage_text, usage_text_LEN, 1, fp);
         break;
 
     default:
     bad_opt:
         exit_code = EXIT_FAILURE;
         fp = stderr;
         fprintf(stderr, opterrmsg, "unknown option", opt_pz);
         pz = usage_text;
         do putc(*pz, stderr);
         while (*(pz++) != '\n');
     }
 
     exit(exit_code);
 }
 
 /**
  * Emit all the macros for doing the char classification tests.
  * "NBBY" is "Number of Bits per BYte".
  *
  * @param bit_count  the number of bits in use
  */
 void
 emit_macros(int bit_count)
 {
     char fill[CLASS_NAME_LIMIT+3];
     int  map_ix = 0;
 
     /*
      * The type to use for the mask depends on the number of bits
      * in the output.  Make the selection and emit the mask typedef.
      */
     {
         char const * mask_type;
         switch (bit_count) {
         case  1 ...  8: mask_type = "uint8_t";  break;
         case  9 ... 16: mask_type = "uint16_t"; break;
         case 17 ... 32: mask_type = "uint32_t"; break;
         case 33 ... 64: mask_type = "uint64_t"; break;
 
         default:
             die("too many char types (%u max)\n", sizeof(long long) * NBBY);
         }
         printf(typedef_mask, mask_type, mask_name);
     }
 
     /*
      *  Make a padding string so our output looks pretty.
      */
     if (max_name_len < CLASS_NAME_LIMIT-2)
         max_name_len += 2;
     memset(fill, ' ', max_name_len);
     fill[max_name_len - 1] = NUL;
 
     for (value_map_t * map = all_map.next; map != NULL; map = map->next) {
         size_t ln = strlen(map->vname);
         char * pz = fill + ln; // padding pointer
         char z[24], y[24]; // 24 >= ceil(log10(1 << 63)) + 1
         snprintf(z, sizeof(z), mask_fmt, map->mask);
 
         if (optimize_code)
             sprintf(y, "%u", map_ix++);
         else
             strcpy(y, z);
 
         printf(macro_def_fmt, map->vname, pz, base_fn_nm, z, y);
         if (add_backup_code)
             printf(backup_def_fmt, map->vname, pz, base_fn_nm, y);
     }
 
     putc('\n', stdout);
 }
 
 /**
  * produce a test main procedure that shows the classsifications.
  * Iterate character values from 0 through 0x7F.
  */
 static void
 emit_test_code(void)
 {
     int ix = 0;
     value_map_t * map;
     printf(testit_fmt, base_ucase);
     for (map = all_map.next; map != NULL; map = map->next) {
         printf(testit_class_names, ix++, map->vname);
     }
     fputs(testit_class_hdrs, stdout);
     for (map = all_map.next, ix=0;
          map != NULL;
          map = map->next) {
         printf(" %02X", ix++);
     }
     fputs(test_loop, stdout);
     for (map = all_map.next, ix=0;
          map != NULL;
          map = map->next) {
         char z[24]; // 24 >= ceil(log10(1 << 63)) + 1
         snprintf(z, sizeof(z), mask_fmt, map->mask);
         printf(each_test, base_fn_nm, z);
         ix++;
     }
     fputs("        putchar('\\n');\n", stdout);
     printf(endtest_fmt, base_ucase);
 }
 
 void
 emit_functions(void)
 {
     if (! table_is_static)
         printf(declare_tbl, mask_name, table_name, TABLE_SIZE);
 
     printf(inline_functions,
            base_fn_nm, mask_name, TABLE_SIZE, table_name);
 
     if (add_backup_code)
         printf(inline_backup, base_fn_nm, mask_name);
 
     if (add_on_text != NULL)
         printf(emit_text_fmt, add_on_text);
 
     if (add_test_code)
         emit_test_code();
 
     printf(endif_fmt, file_guard);
 }
 
 void
 emit_opt_functions(void)
 {
     if (! table_is_static)
         printf(declare_opt_tbl, mask_name, base_fn_nm,
                TABLE_SIZE, base_ucase, total_map_ct);
 
     printf(inline_opt_functions, base_fn_nm, mask_name, TABLE_SIZE);
 
     if (add_backup_code)
         printf(inline_opt_backup, base_fn_nm);
 
     if (add_on_text != NULL)
         printf(emit_text_fmt, add_on_text);
 
     if (add_test_code)
         emit_test_code();
 
     printf(endif_fmt, file_guard);
 }
 
 void
 emit_table(int bit_count)
 {
     char const * type_pz;
     char * fmt;
 
     int  ix = 0;
 
     /**
      * How many entries can be initialized on one line?  We can do four
      * uint8_t's, uint16_t's or even uint32_t's.  Only two uint64_t's tho.
      */
     int  init_ct  = (bit_count > 32) ? 2 : 4;
     int  entry_ct = init_ct;
 
     if (table_is_static)
         printf(start_static_table_fmt, mask_name, table_name, TABLE_SIZE);
     else
         printf(start_table_fmt, data_guard, mask_name, table_name, TABLE_SIZE);
 
     for (;;) {
         if (isprint(ix))
             printf(" /* %c */ ", (char)ix);
 
         else switch (ix) {
             case NUL:  fputs(" /*NUL*/ ", stdout); break;
             case '\a': fputs(" /*BEL*/ ", stdout); break;
             case '\b': fputs(" /* BS*/ ", stdout); break;
             case '\t': fputs(" /* HT*/ ", stdout); break;
             case '\n': fputs(" /* NL*/ ", stdout); break;
             case '\v': fputs(" /* VT*/ ", stdout); break;
             case '\f': fputs(" /* FF*/ ", stdout); break;
             case '\r': fputs(" /* CR*/ ", stdout); break;
             case 0x1B: fputs(" /*ESC*/ ", stdout); break;
             default:  printf(" /*x%02X*/ ", ix);   break;
             }
 
         printf(mask_fmt, all_map.values[ix]);
 
         if (++ix >= TABLE_SIZE)
             break;
 
         putc(',', stdout);
 
         if (--entry_ct <= 0) {
             putc('\n', stdout);
             putc(' ', stdout);
             entry_ct = init_ct;
         }
     }
 
     fputs(end_table, stdout);
 
     if (optimize_code) {
         static char const fmtfmt[] = "    %%s, /* %%-%us*/\n";
         char const * static_pfx  = table_is_static ? "static " : "";
         char const * locking_fmt = pthread_code ? pthread_locking : no_locking;
         char fmt[256];
 
         printf(optimize_fmt, static_pfx, base_fn_nm, total_map_ct);
         snprintf(fmt, sizeof(fmt), fmtfmt, (unsigned int)max_name_len);
 
         for (value_map_t * map = all_map.next; map != NULL; map = map->next) {
             char z[24]; // 24 >= ceil(log10(1 << 63)) + 1
             snprintf(z, sizeof(z), mask_fmt, map->mask);
             printf(fmt, z, map->vname);
         }
         printf(locking_fmt, base_fn_nm);
         printf(fill_opt_fmt, base_fn_nm, static_pfx);
     }
 
     if (! table_is_static)
         printf(endif_fmt, data_guard);
 }
 
 int
 read_data(void)
 {
     int bit_num = 0;
 
     while (fgets(buffer, BUF_SIZE, stdin) != NULL) {
         char nm_buf[CLASS_NAME_LIMIT+1];
         char * scan = trim(buffer);
         value_map_t * map = NULL;
         if (scan == NULL)  continue;
 
         scan = get_name(scan, nm_buf, sizeof(nm_buf));
         if (curr_name_len > max_name_len)
             max_name_len = curr_name_len;
 
         while (scan != NULL) {
             switch (*scan) {
             case NUL: goto end_while;
             case '"':
                 if (map == NULL)
                     map  = new_map(nm_buf);
                 if (map->bit_no == ~0) {
                     map->mask  |= 1 << bit_num;
                     map->bit_no = bit_num;
                     bit_num++;
                 }
                 scan = scan_quoted_value(scan+1, map, VAL_ADD);
                 break;
 
             case ' ': case '\t':
                 while (isspace(*scan))  scan++;
                 break;
 
             case '+':
             case '-':
                 if (map == NULL)
                     map  = new_map(nm_buf);
                 if (scan[1] == '"')
                      scan = scan_quoted_value(scan+2, map, VAL_REMOVE);
                 else scan = copy_another_value(scan, map);
                 break;
 
             case '%':
                 scan = parse_directive(scan);
                 break;
 
             default:
                 die("value must start with quote or '+' or '-'\n\t%s\n", scan);
             }
         } end_while:;
     }
 
     return bit_num;
 }
 
 char *
 eat_a_byte(char * scan, int * byte_val)
 {
     if (*scan != '\\') {
         *byte_val = (unsigned char)*(scan++);
 
     } else {
         int chval = '\\';
 
         switch (*++scan) {
         case NUL: break;
         case '\\':               scan++; break;
         case 'a':  chval = '\a'; scan++; break;
         case 'b':  chval = '\b'; scan++; break;
         case 't':  chval = '\t'; scan++; break;
         case 'n':  chval = '\n'; scan++; break;
         case 'v':  chval = '\v'; scan++; break;
         case 'f':  chval = '\f'; scan++; break;
         case 'r':  chval = '\r'; scan++; break;
         case '"':  chval = '"';  scan++; break;
         case '0' ... '7':
             {
                 char octbuf[4];
                 octbuf[0] = *(scan++);
                 if ((*scan < '0') || (*scan > '7'))
                     octbuf[1] = NUL;
                 else {
                     octbuf[1] = *(scan++);
                     if ((*scan < '0') || (*scan > '7'))
                         octbuf[2] = NUL;
                     else {
                         octbuf[2] = *(scan++);
                         octbuf[3] = NUL;
                     }
                 }
                 chval = (int)strtoul(octbuf, NULL, 8);
                 if (chval > 0xFF) {
                     scan -= 2;
                     goto invalid_escape;
                 }
                 break;
             }
 
         case 'x': case 'X':
         {
             char hexbuf[4];
             if (! isxdigit(*++scan)) goto invalid_escape;
             hexbuf[0] = *(scan++);
             if (! isxdigit(*scan))
                 hexbuf[1] = NUL;
             else {
                 hexbuf[1] = *(scan++);
                 hexbuf[2] = NUL;
             }
             chval = (int)strtoul(hexbuf, NULL, 16);
             break;
         }
 
         default:
         invalid_escape:
             die("invalid escape sequence:  %s\n", scan-1);
         }
 
         *byte_val = chval;
     }
 
     return scan;
 }
 
 
 char *
 scan_quoted_value(char * scan, value_map_t * map, quoted_val_action_t act)
 {
     value_bits_t mask = 1 << map->bit_no;
     if (act == VAL_REMOVE)
         mask = ~mask;
 
     for (;;) {
         int lo_ix, hi_ix;
 
         switch (*scan) {
             case NUL: return scan;
             case '"': return scan + 1;
             default:  break;
         }
         scan = eat_a_byte(scan, &lo_ix);
         if ((*scan == '-') && (scan[1] != NUL) && (scan[1] != '"')) {
             scan = eat_a_byte(scan + 1, &hi_ix);
         } else {
             hi_ix = lo_ix;
         }
 
         switch (act) {
         case VAL_ADD:
             do {
                 all_map.values[lo_ix] |= (map->values[lo_ix] = mask);
             } while (++lo_ix <= hi_ix);
             break;
 
         case VAL_REMOVE:
             map->values[lo_ix] = 0;
             do {
                 all_map.values[lo_ix] &= mask;
             } while (++lo_ix <= hi_ix);
         }
     }
 }
 
 char *
 trim(char * buf)
 {
     while (isspace(*buf))  buf++;
 
     {
         char * pe = buf + strlen(buf);
         while ((pe > buf) && isspace(pe[-1]))  pe--;
         *pe = NUL;
     }
 
     switch (*buf) {
     case NUL:
     case '#':
         return NULL;
     default:
         return buf;
     }
 }
 
 char *
 get_name(char * scan, char * nm_buf, size_t buf_size)
 {
     char * buf_end = nm_buf + buf_size - 2;
     size_t nm_len = 0;
 
     while (isalnum(*scan) || (*scan == '_') || (*scan == '-')) {
         int ch = *(scan++);
         if (ch == '-')
             ch = '_';
         else if (islower(ch))
             ch = toupper(ch);
 
         *(nm_buf++) = ch;
         nm_len++;
 
         if (nm_buf >= buf_end)
             nm_buf--;
     }
     if (scan == nm_buf)
         die("input line does not start with a name:\n\t%s\n", nm_buf);
 
     while (isspace(*scan)) scan++;
 
     *nm_buf = NUL;
     curr_name_len = nm_len;
 
     return scan;
 }
 
 value_map_t *
 new_map(char * name)
 {
     value_map_t * map = malloc(sizeof(*map));
     memset(map, 0, sizeof(*map));
     *end_map = map;
     end_map  = &(map->next);
     strcpy(map->vname, name);
     map->bit_no = ~0;
     total_map_ct++;
     return map;
 }
 
 char *
 copy_another_value(char * scan, value_map_t * map)
 {
     int  add_in = (*scan == '+');
     char z[CLASS_NAME_LIMIT+1];
     value_map_t * mp = all_map.next;
     scan = get_name(scan+1, z, sizeof(z));
 
     for (;;mp = mp->next) {
         if (mp == NULL)
             die("No entry named %s\n", z);
 
         if (strcmp(z, mp->vname) == 0)
             break;
     }
 
     if (add_in) {
         map->mask |= mp->mask;
     }
 
     else if (map->bit_no == ~0)
         die("You cannot remove bits that are not there.\n");
 
     else {
         value_bits_t   mask = ~(1 << map->bit_no);
         value_bits_t   skip = mp->mask;
         value_bits_t * this = map->values;
         value_bits_t * all  = all_map.values;
 
         int ct = sizeof(mp->values) / sizeof(mp->values[0]);
 
         do  {
             /*
              *  We are negating, so if the named value has this bit set,
              *  turn off the current bit in the global mask and turn off
              *  all bits in the for-this-named-value array of masks.
              */
             if ((*all & skip) != 0) {
                 *all &= mask;
                 *this = 0;
             }
 
             this++; all++;
         } while (--ct > 0);
     }
 
     return scan;
 }
 
 static void
 make_define_name(char * ptr)
 {
     while (*ptr) {
         if (islower(*ptr))
             *ptr = toupper(*ptr);
         else if (! isalnum(*ptr))
             *ptr = '_';
 
         ptr++;
     }
 }
 
 char *
 parse_directive(char * scan)
 {
     while (isspace(*++scan))
         /* skip leading '%' and white space */;
 
     if (! isalpha(*scan))
         die("directives must begin with an alphabetic character:  %s\n", scan);
 
     return handle_cm_opt_disp(scan);
 }
 
 static char *
 add_text(char ** buff, size_t * sz, char * curr, char * add, int pfx)
 {
     size_t len = (add == NULL) ? 0 : strlen(add);
     char * e = *buff + *sz - 4;
 
     if (*buff + len >= e) {
         size_t o = curr - *buff;
         *sz += BUF_SIZE;
         *buff = realloc(*buff, *sz);
         curr  = *buff + o;
     }
 
     if (pfx)
         *(curr++) = ' ', *(curr++) = '*';
 
     if (len == 0) {
         *(curr++) = '\n';
 
     } else {
         if (pfx)
             *(curr++) = ' ', *(curr++) = ' ';
 
         memcpy(curr, add, len);
         curr += len;
     }
 
     return curr;
 }
 
 static char *
 load_text(int pfx)
 {
     char * scan_in;
     char * com_buf      = malloc(BUF_SIZE);
     char * scan_out     = com_buf;
     size_t com_buf_size = BUF_SIZE;
     int    line_ct      = 0;
     int    blank_ct     = 0;
 
     for (;;) {
         if (fgets(buffer, BUF_SIZE, stdin) == NULL)
             die("incomplete comment section");
 
         scan_in = buffer;
         while (isspace(*scan_in)) scan_in++;
 
         /*
          *  if scan is NULL, we've got a comment
          */
         if (*scan_in == NUL) {
             blank_ct++;
             continue;
         }
 
         if (*scan_in == '%')
             break;
 
         if (line_ct++ == 0) {
             blank_ct = 0;
 
         } else while (blank_ct > 0) {
                 scan_out =
                     add_text(&com_buf, &com_buf_size, scan_out, NULL, pfx);
                 blank_ct--;
                 line_ct++;
             }
 
         scan_out = add_text(&com_buf, &com_buf_size, scan_out, scan_in, pfx);
     }
 
     *scan_out  = NUL;
     return com_buf;
 }
 
 /* * * * * * * * * * * * * * * *
 
    The following handler functions document and define the embedded
    directives.  The first line of the comment must be exactly
    "handle" + one space + directive name + one space + "directive".
    Dispatch tables and documentation is derived from this.
 
  * * * * * * * * * * * * * * * */
 
 /**
  * handle file directive.
  *
  * Specifies the output file name.  The multi-inclusion guard is derived
  * from this name.  If %file is not specified, that guard defaults to
  * CHAR_MAPPER_H_GUARD.  The default output is to stdout.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_file(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     char * pz;
     size_t fname_len;
 
     if (! isspace(*scan)) die(bad_directive, scan-4);
 
     while (isspace(*scan))   scan++;
 
     if (freopen(scan, "w", stdout) != stdout)
         die("fs error %d (%s) reopening %s as stdout\n", errno,
             strerror(errno), scan);
 
     fname_len = strlen(scan);
     file_guard = pz = malloc(fname_len + guard_fmt_LEN);
     sprintf(pz, guard_fmt, scan);
     make_define_name(pz);
 
     out_file_nm = pz = malloc(fname_len + 1);
     strcpy(out_file_nm, scan);
 
     *scan = NUL;
     return scan;
 }
 
 /**
  * handle comment directive.
  *
  * Specifies the text to insert in a comment block at the head of the output
  * file.  The comment text starts on the next line and ends with the first
  * input line with a percent ('%') in the first column.  The default is:
  * char-mapper Character Classifications
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_comment(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     commentary = load_text(1);
     *scan = NUL;
     return scan;
 }
 
 /**
  * handle emit directive.
  *
  * Collect text to be emitted at the end of the header.  Like "%comment",
  * the block of text also ends with a line with a '%' character in column 1.
  *
  * @param scan current scan point
  * @returns    The '%' starting the next directive.
  */
 char *
 handle_emit(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     add_on_text = load_text(0);
     *scan = NUL;
     return scan;
 }
 
 /**
  * handle table directive.
  *
  * Specifies the name of the output table.  If not specified, it defaults to
  * the base file name, suffixed with "_table" and "char_type_table" if %file
  * is not specified.  Normally, this table is "extern" in scope, but may be
  * made static by specifying an empty %guard.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_table(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     char * pz;
 
     if (! isspace(*scan)) die(bad_directive, scan-5);
 
     while (isspace(*scan))   scan++;
     table_name = pz = strdup(scan);
     for (;*pz; pz++) {
         if (! isalnum((int)*pz))
             *pz = '_';
     }
 
     return scan + strlen(scan);
 }
 
 /**
  * handle optimize directive.
  *
  * Specifies setting up optimization tables for span/break scanning operations
  * on character classes.  A static array of pointers is defined and populated
  * on the first use of a scan/break on each character classification.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_optimize(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     optimize_code = true;
     return scan + strlen(scan);
 }
 
 /**
  * handle pthread directive.
  *
  * Specifies pthread locking on allocation of optimization maps.  This has no
  * effect unless %optimize has been specified.  Pthread-ed multi-threaded
  * applications *must* specify this if optimization maps are emitted.
  * Spanning maps get allocated dynamically when first used.  Only one thread
  * should do the work.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_pthread(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     pthread_code = true;
     return scan + strlen(scan);
 }
 
 /**
  * handle guard directive.
  *
  * Specifies the name of a '#ifdef' guard protecting the compilation of
  * the bit mask table.  One compilation unit must '#define' this name.
  * The default is the table name surrounded by "DEFINE_" and "_TABLE".
  * If empty, the output table is unguarded and made static in scope.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_guard(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     char * pz;
 
     if (! isspace(*scan)) {
         table_is_static = true;
         return scan;
     }
 
     while (isspace(*scan))   scan++;
     data_guard = pz = strdup(scan);
     make_define_name(pz);
     return scan + strlen(scan);
 }
 
 /**
  * handle backup directive.
  *
  * Specifies emitting code to backup over matching text at the end of a string.
  * The macro takes two arguments:  a start point and an end point.
  * The "end point" is returned after backing up over skipped characters,
  * but it will be at or after the start pointer.  The second pointer may also
  * be NULL or set to the start pointer.  In that case, it is advanced to the
  * next NUL byte at or after where the start pointer points.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_backup(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     add_backup_code = true;
     return scan + strlen(scan);
 }
 
 /**
  * handle test directive.
  *
  * Specifies that a main procedure is to be emitted under an ifdef guard.
  * The first few lines of the output can also be processed by a Bourne
  * shell to compile and test that main procedure.  That shell script is
  * invisible to the C compiler.  It is under a "#if 0" guard.
  *
  * @param scan current scan point
  * @returns    end of guard scan
  */
 char *
 handle_test(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     add_test_code = true;
     return scan + strlen(scan);
 }
 
 /**
  * handle invalid directive.
  *
  * This function gets called when input is messed up.  This function does not
  * return.
  *
  * @param scan current scan point
  * @returns    not
  */
 char *
 handle_invalid(cm_opt_enum_t id, char const * txt)
 {
     char * scan = (char *)(unsigned long)txt;
     die(bad_directive, scan);
 }
 /*
  * Local Variables:
  * mode: C
  * c-file-style: "stroustrup"
  * indent-tabs-mode: nil
  * End:
  * end of char-mapper.c */