[= AutoGen5 Template -*- Mode: C -*-

h
c

=]
[= INVOKE preamble \=]
[= CASE (suffix)    =][=#

PURPOSE:
   This template will produce a header file and one or two source files.
   You supply a list of commands as values for "cmd" and it will produce
   an enumeration of these commands and code that will match strings against
   these command names.  The input command name need only be as long as
   needed for a unique partial match.  For example, if there is only one
   command that starts with "x", then the single letter "x" is sufficient
   for a unique match.

YOU SUPPLY:
   an AutoGen definitions file.  It must contain a list of values for
   "cmd".  e.g.:  cmd = first, second, third;

   It may also contain "abbrev", "c-text", "dispatch", "ident" and
   "copyright" values.
  
   "copyright" See the AutoOpts documentation for its usage.
   "ident"     is inserted at the top of all output files.
   "c-text"    is inserted at the top of the .c files
   "dispatch"  describes the format of handler functions:
      "fmt"    how to construct the name from the command name.  e.g. "cmd_%s"
      "arg"    The type and name of argument(s) to handler functions
      "ret"    the function return type.
   The handler functions are expected to have the following profile,
   assuming we are expanding the "cmd" named "mumble" and "dispatch" has:
      ret = int;
      fmt = 'cmd_%s_hdlr';
      arg = 'void * cookie';

   then:

      extern int cmd_mumble_hdlr(char const * cmd, void * cookie);
   though "cmd" will point past the command name.

   "abbrev"    is used to construct the name for an invalid value handler

THE TEMPLATE PRODUCES:

   <base-name> is based on the name of your definitions file:

   <base-name>.h    The enumeration of the commands in the form:
                    <BASE_NAME>_<COMMAND> of type <base_name>_enum_t

                    External declarations of the two or three generated
                    procedures.  (dispatch_<base_name> is produced IFF
                    there is a "dispatch" value.

   <base-name>.c    The code for the two or three procedures.

   <base-name>-hdlr.c  This is produced if there is a "dispatch" value and if
                    the environment variable EMIT_DISPATCH is set.

   If your commands include one named, "help", then an input string starting
   with a "?" will match the help command.

   If your commands include one named, "null", then an empty or all spaces
   input string will match the null command.

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

=][=

== h                =][=

INVOKE initialize   =]

#include <stdio.h>

typedef enum { [= (. TYPE) =]_INVALID = 0,
[=

 (shell (string-append
"echo \"${clist}\" | tr a-z A-Z | "
   "columns -I4 --spread=1 --sep=, --format='" TYPE "_%s'" ))

=]
} [= (. enum-nm) =];

#define [= (. TYPE) =]_CT [= (count "cmd") =]

extern [= (. enum-nm) =]
[= (. tp-nm) =]_enum(char const * name, char const ** next);
extern char const *
[= (. tp-nm) =]_name([= (. enum-nm) =] cmd);[=

IF (exist? "dispatch")  =][=
   FOR dispatch         =][=

      IF (define arg-list  "char const * cmd")
         (define call-list "cmd")
         (exist? "dispatch.arg")        =][=

         (out-push-new) =][=

         FOR arg        =], [= arg =][=
            (set! call-list (string-append call-list ", "
                  (shellf "echo '%s' | sed $'s/.*[ \t*]//'" (get "arg")) ))
            =][=
         ENDFOR         =][=

         (set! arg-list (string-append arg-list (out-pop #t))) =][=

      ENDIF

=]
extern [= ret =] dispatch_[= (. tp-nm) =]([= (. arg-list) =]);[=

   ENDFOR dispatch      =][=

ENDIF

=]

#endif /* [= (. header-guard) =] */
[=#

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

=][=

== c            \=]

#include <ctype.h>
#include <string.h>
#include "[= (. header-file) =]"
[= dispatch.c-text =][=

INVOKE construct-string-table
=]
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 *  The hash code was generated by "gperf(1GNU)" from the following:
 *
[= ;; */
  (make-gperf str-table-name (shell "echo \"${clist}\""))
  (shellf "
     sed '/^int main(/,$d;s/^/ *  /' ${gpdir}/%1$s.gperf
     echo ' */'
     echo
     sed -e '/^int main(/,$d' \
         -e '/^#line/d' \
         -e '/^#if !/,/^#endif/d' \
         -e '/^[ \t]*\\/\\*.*\\*\\/$/d' \
         -e '/^$/d' ${gpdir}/%1$s.c" str-table-name)
=]
/*
 *  END of gperf code
 *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

[= (. enum-nm) =]
[= (. tp-nm) =]_enum(char const * name, char const ** next)
{
    size_t nmlen = 0;
    int    hi_ix = [= (- cmd-ct 1) =];
    int    lo_ix = 0;
    int    av_ix = 0;
    char   name_buf[[= (+ max-len 1) =]];

    /*
     * Skip leading white space and figure out how many name characters
     * there are.  It cannot be longer than the longest name in our table.
     */
    while (isspace(*name))   name++;

    if (isalpha(*name)) {
        char const * ps = name;
        char * pd = name_buf;
        for (;;) {
            char ch = *(ps++);
            if (isupper(ch))
                *(pd++) = _tolower(ch);

            else if (isalnum(ch))
                *(pd++) = ch;

            else switch (ch) {
                    case '-':
                    case '_': *(pd++) = '_'; break;
                    default:  goto name_scan_done;
                }

            if (++nmlen > [= (. max-len) =])
                return [= (. TYPE) =]_INVALID;
        } name_scan_done:;
        *pd = '\0';
    }

    /*
     * If there were no name characters found, bail out now.[=
IF (> (string-length special-char-handling) 1) =]
     * Check for commands triggerd by a single non-alpha character:[=
ENDIF =]
     */
    if (nmlen == 0) {[=
    (if (> (string-length special-char-handling) 1)
        (string-append "\n        switch (*name) {" special-char-handling
                       "        }" ))  =]
        return [= (. TYPE) =]_INVALID;
    }

    /*
     *  First, let's try hashing the name.  If we have an exact match, we'll
     *  hash straight to it.  Don't bother if name shorter than shortest name.
     */
    if (nmlen >= [= (. min-len) =]) {
	t_index * pi = in_word_set(name_buf, nmlen);

	if (pi != NULL) {
	    av_ix = pi->idx - 1;
	    goto return_successfully;
	}
    }

    /*
     *  An exact match is not possible.  Check for partial match.
     */
    for (;;) {
        int cmp;
        char const * pz;

        av_ix = (hi_ix + lo_ix) / 2;
        pz    = [= (. str-table-name) =]_ap[av_ix];
        cmp   = strncmp(pz, name_buf, nmlen);

        if (cmp == 0) break; /* found a partial */

        if (cmp < 0)
             lo_ix = av_ix + 1;
        else hi_ix = av_ix - 1;

        if (lo_ix > hi_ix)
            return [= (. TYPE) =]_INVALID;
    }

    /*
     *  See if there are any other partial matches.
     */
    if (av_ix > 0) {
	int cmp = strncmp([= (. str-table-name) =]_ap[av_ix-1], name_buf, nmlen);
        if (cmp == 0)
            return [= (. TYPE) =]_INVALID;
    }

    if (av_ix < [= (- cmd-ct 1) =]) {
	int cmp = strncmp([= (. str-table-name) =]_ap[av_ix+1], name_buf, nmlen);
	if (cmp == 0)
	    return [= (. TYPE) =]_INVALID;
    }

 return_successfully:

    if (next != NULL) {
	name += nmlen;
	while ((*name == '\t') || (*name == ' '))   name++;
	*next = name;
    }
    return ([= (. enum-nm) =])(av_ix+1);
}

/*
 * Translate the command code into the name
 */
char const *
[= (. tp-nm) =]_name([= (. enum-nm) =] cmd)
{
    unsigned int ix = (unsigned int)cmd - 1;
    return (ix > [= (- cmd-ct 1) =]) ? [= (. str-table-name) =] : [=
           (. str-table-name) =]_ap[ix];
}[=

INVOKE dispatcher

=][=

ESAC prefix =]
/* end of [= (out-name) =] */
[=#

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Dispatcher   =][=

 (if (< 1 (count "dispatch"))
     (error "too many dispatch descriptions")) =][=

FOR dispatch

=]

typedef [= ret =] (handle_[= (. tp-nm) =]_t)([= (. arg-list) =]);

extern handle_[= (. tp-nm) =]_t [=
    (define  cmd-fmt (if (exist? "fmt") (get "fmt") "do_%s"))

    (define inval-cmd (string-append "inval_"
            (if (exist? "abbrev") (get "abbrev") tp-nm)  ))
    (sprintf cmd-fmt inval-cmd) =],
[=
(define cmd-procs (shellf "echo \"${clist}\" | \
columns -I4 --spread=1 --sep=, --format='%s'" cmd-fmt (get "type") ))

cmd-procs

=];

static handle_[= (. tp-nm) =]_t * const hdl_procs[[= (+ 1 cmd-ct) =]] = {
    [= (sprintf cmd-fmt inval-cmd) =],
[= (. cmd-procs) =] };

[= ret =]
dispatch_[= (. tp-nm) =]([= (. arg-list) =])
{
    [= (. enum-nm) =]     id  = [= (. tp-nm) =]_enum(cmd, &cmd);
    handle_[= (. tp-nm) =]_t * hdl = hdl_procs[id];
    return (*hdl)([= (. call-list) =]);
}[=

   IF (getenv "EMIT_DISPATCH")  =][=
      INVOKE Handler    =][=
   ENDIF                =][=

ENDFOR dispatch         =][=

ENDDEF dispatcher

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Handler          =][=

 (out-push-new (string-append (base-name) "-hdlr.c"))
 (define fmt-fmt (sprintf
 "handling %%-%ds (%%d) cmd - args:  '%%%%s'\\n" max-len))
 
 (set-writable)

\=]
[= INVOKE preamble      =]
#include <stdio.h>
#include "[= (. header-file)    =]"
[= dispatch.c-text =][=

   FOR cmd              =][=
      IF (not (= (get "cmd") "help")) =]

[= ret =]
[= (sprintf cmd-fmt (get "cmd")) =]([= (. arg-list) =])
{
#ifdef DEBUG
    printf("[= (sprintf fmt-fmt (get "cmd") (+ 1 (for-index)))
            =]", cmd);
#else
    fputs("[= cmd =]\n", stdout);
#endif
    return ([= ret =])0;
}
[=

      ENDIF not "help" \=]
[= ENDFOR cmd          \=]
[= IF (match-value? = "cmd" "help")     =][=
      IF (getenv "SHOW_NOHELP")         =][=
                (define show-nohelp #t) =][=
      ELSE =][= (define show-nohelp #f) =][=
      ENDIF=]

[= ret =]
[= (define help-fmt (sprintf "%%-%ds%%s\n"
      (- (+ max-len 4) (remainder max-len 4)) ))
   (define cmd-name "")

   (sprintf cmd-fmt "help") =]([= (. arg-list) =])
{
    static char const h_txt[ [=
        (out-push-new)      =][=
     FOR cmd                =][=
        (set! cmd-name (get "cmd"))
        (if (exist? (string-append cmd-name "-help"))
            (sprintf help-fmt cmd-name (get (string-append cmd-name "-help")) )
            (if (= cmd-name "help")
                (sprintf help-fmt cmd-name "Display this help text")
                (if show-nohelp
                    (sprintf help-fmt cmd-name "No provided help"))
        )   )               =][=
     ENDFOR each "cmd"      =][=

    (define help-text (out-pop #t))
    (string-length help-text)   =] ] =
       [= (c-string help-text)  =];
    if (*cmd != '\0')
        printf("help args:  %s\n", cmd);
    fwrite(h_txt, [= (string-length help-text) =], 1, stdout);
    return ([= ret =])0;
}
[= ENDIF have-a-help cmd    =]

[= ret =]
[= (sprintf cmd-fmt inval-cmd)  =]([= (. arg-list) =])
{
    printf("handling invalid (%d) command:  '%s'\n",
           [= (. TYPE) =]_INVALID, cmd);
    return ([= ret =])0;
}

int
main( int argc, char** argv )
{
    while (--argc > 0)
        dispatch_[= (. tp-nm) =]( *++argv[=FOR arg=], 0[=ENDFOR=] );
    return 0;
}
[= (out-pop)
   (set-writable #f)    =][=

ENDDEF Handler

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Preamble                 =][=

(dne " * " "/*")                =][=

  IF (exist? "copyright")       =][=
  CASE (get "copyright.type")   =][=

    =  gpl  =]
 *
[= (gpl  prog-name " * " )      =][=

    = lgpl  =]
 *
[=(lgpl prog-name (get "copyright.owner") " * " ) =][=

    =  bsd  =]
 *
[=(bsd  prog-name (get "copyright.owner") " * " ) =][=

    = note  =]
 *
 * Copyright (c) [= copyright.years =] by [= copyright.owner =]
 * All rights reserved.
 *
[=(prefix " * " (get "copyright.text"))=][=

    *       =] * <<indeterminate license type>>[=

  ESAC                          =][=
  ENDIF  copyright exists       =]
 */
[= (if (exist? "ident")
       (string-append (get "ident") "\n")) =][=

ENDDEF Preamble

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE spec-char-cmd    =]
        case '[= spec-char =]':  /* [= cmd =] command alias */
             av_ix = [= (. TYPE) =]_[= (string-upcase! (get "cmd")) =] - 1;[=
             IF (not (= (get "cmd") "null")) =]
             name++;[=
             ENDIF =]
             goto return_successfully;
[=

ENDDEF spec-char-cmd

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE Initialize   =][=

   (define tp-nm    (string-downcase! (string->c-name! (base-name))))
   (define enum-nm  (string-append tp-nm "_enum_t"))
   (define TYPE     (string-upcase tp-nm))
   (define name-len 0)
   (define max-len  0)
   (define min-len  256)
   (define cmd-ct   (count "cmd"))

   (shell (string-append
      "clist=`sort <<_EOF_\n" (join "\n" (stack "cmd")) "\n_EOF_\n`" ))

   (make-header-guard "select") =][=
   (out-push-new)               =][=

   FOR cmd          =][=
     (set! name-len (string-length (get "cmd")))
     (if (> name-len max-len)
         (set! max-len name-len) )
     (if (< name-len min-len)
         (set! min-len name-len) ) =][=

     CASE cmd       =][=
     =  help        =][= INVOKE spec-char-cmd  spec-char = "?"   =][=
     =  null        =][= INVOKE spec-char-cmd  spec-char = "\\0" =][=
     ESAC cmd       =][=

   ENDFOR           =][=

   (define special-char-handling (out-pop #t))
   (if (not (defined? 'special-char-handling ;; '
       )    )
       (define special-char-handling "") ) =][=

ENDDEF Initialize

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ =][=

DEFINE construct-string-table =][=

   (define str-table-name tp-nm)
   (string-table-new str-table-name)
   (string-table-add str-table-name "** INVALID **")
   (out-push-new)
   (define ix 0)
   (shell "set -- ${clist}")

   =][=

   WHILE `test $# -gt 0 && echo true` =][=

     (set! ix (string-table-add str-table-name (shell "echo $1 ; shift")))
     (sprintf "%s + %d,\n" str-table-name ix) =][=

   ENDWHILE

   =][=

   (out-suspend "main")
   (emit-string-table str-table-name)
   (ag-fprintf 0 "\nchar const * const %s_ap[%d] = {\n" str-table-name cmd-ct)
   (out-resume "main")
   (shell (string-append
     "(columns -I4 --spread=1 | \
       sed '$s/, *$/ };/'
     ) <<_EOF_\n" (out-pop #t) "_EOF_"))  =][=

ENDDEF construct-string-table

\=]