[= 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 \=]