chiark / gitweb /
*.c: General spring-clean of the coding style.
[anag] / anag.c
diff --git a/anag.c b/anag.c
index 56aa565e9f82f33654c51a525b5e703d33925059..315930d3f3872867953caf67b5629f618f899692 100644 (file)
--- a/anag.c
+++ b/anag.c
@@ -1,13 +1,11 @@
 /* -*-c-*-
- *
- * $Id: anag.c,v 1.6 2003/09/15 02:48:54 mdw Exp $
  *
  * Main driver for anag
  *
  * (c) 2001 Mark Wooding
  */
 
-/*----- Licensing notice --------------------------------------------------* 
+/*----- Licensing notice --------------------------------------------------*
  *
  * This file is part of Anag: a simple wordgame helper.
  *
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
- * 
+ *
  * Anag 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 Anag; if not, write to the Free Software Foundation,
  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/*----- Revision history --------------------------------------------------* 
- *
- * $Log: anag.c,v $
- * Revision 1.6  2003/09/15 02:48:54  mdw
- * Monoalphabetic match filter.
- *
- * Revision 1.5  2002/08/11 12:58:09  mdw
- * Added support for regular expression matching, if supported by the C
- * library.
- *
- * Revision 1.4  2001/02/19 19:18:50  mdw
- * Minor big fixes to parser.
- *
- * Revision 1.3  2001/02/16 21:45:19  mdw
- * Be more helpful.  Improve full help message.  Special-case error for
- * empty command strings.
- *
- * Revision 1.2  2001/02/07 09:09:11  mdw
- * Fix spurious error when `-file' is used.
- *
- * Revision 1.1  2001/02/04 17:14:42  mdw
- * Initial checkin
- *
- */
-
 /*----- Header files ------------------------------------------------------*/
 
 #include "anag.h"
@@ -62,14 +35,10 @@ static const char *file = DICTIONARY;
 /*----- Help text functions -----------------------------------------------*/
 
 static void usage(FILE *fp)
-{
-  pquis(fp, "Usage: $ [-f file] expression\n");
-}
+  { pquis(fp, "Usage: $ [-f file] expression\n"); }
 
 static void version(FILE *fp)
-{
-  pquis(fp, "$, version " VERSION "\n");
-}
+  { pquis(fp, "$, version " VERSION "\n"); }
 
 static void help(FILE *fp)
 {
@@ -99,12 +68,20 @@ The basic tests in the expression are:\n\
 -regexp REGEXP         matches with an (extended) regular expression\n\
 "
 #endif
+#if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
+"\
+-pcre REGEXP           matches with a Perl-like regular expression\n\
+"
+#endif
 "\
+-length [+|-]N         matches if length is [at least|at most] N\n\
+-longest               output longest matches found here\n\
+-shortest              output shortest matches found here\n\
 \n\
 These simple tests can be combined using the operators `-a', `-o' and `-n'\n\
 (for `and', `or' and `not'; they may also be written `&', `|' and `!' if\n\
 you like), and grouped using parentheses `(' and `)'.\n\
-", fp);
+", fp); /*"*/
 }
 
 /*----- The options parser ------------------------------------------------*/
@@ -122,7 +99,8 @@ enum {
   O_HELP, O_VERSION, O_USAGE,
   O_FILE,
   O_AND, O_OR, O_NOT, O_LPAREN, O_RPAREN,
-  O_ANAG, O_SUBG, O_WILD, O_TRACK, O_REGEXP, O_MONO,
+  O_ANAG, O_SUBG, O_WILD, O_TRACK, O_REGEXP, O_PCRE, O_MONO, O_LENGTH,
+  O_LONGEST, O_SHORTEST,
   O_EOF
 };
 
@@ -146,7 +124,7 @@ static const struct opt opttab[] = {
   { "or",              0,      OF_SHORT,       O_OR },
   { "not",             0,      OF_SHORT,       O_NOT },
 
-  /* --- Actual matching oeprations -- do something useful --- */
+  /* --- Actual matching operations -- do something useful --- */
 
   { "anagram",         1,      0,              O_ANAG },
   { "subgram",         1,      0,              O_SUBG },
@@ -156,6 +134,12 @@ static const struct opt opttab[] = {
 #ifdef HAVE_REGCOMP
   { "regexp",          1,      0,              O_REGEXP },
 #endif
+#if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
+  { "pcre",            1,      0,              O_PCRE },
+#endif
+  { "length",          1,      0,              O_LENGTH },
+  { "longest",         0,      0,              O_LONGEST },
+  { "shortest",                0,      0,              O_SHORTEST },
 
   /* --- End marker --- */
 
@@ -187,15 +171,12 @@ static unsigned nextopt(const char *const **arg)
     size_t sz;
     const char *p;
 
-    /* --- Pick the next option off the front --- */
-
+    /* Pick the next option off the front. */
     *arg = av + ai;
-    if (ai >= ac)
-      return (O_EOF);
+    if (ai >= ac) return (O_EOF);
     p = av[ai++];
 
-    /* --- Cope with various forms of magic --- */
-
+    /* Cope with various forms of magic. */
     if (p[0] != '-') {
       if (!p[1]) switch (*p) {
        case '&': return (O_AND);
@@ -207,65 +188,44 @@ static unsigned nextopt(const char *const **arg)
       goto bad;
     }
 
-    /* --- Now cope with other sorts of weirdies --- *
-     *
-     * By the end of this, a leading `-' or `--' will have been stripped.
+    /* Now cope with other sorts of weirdies.  By the end of this, a leading
+     * `-' or `--' will have been stripped.
      */
-
     p++;
-    if (!*p)
-      goto bad;
-    if (*p == '-')
-      p++;
+    if (!*p) goto bad;
+    if (*p == '-') p++;
     if (!*p) {
-      if (ai < ac)
-       die("syntax error near `--': rubbish at end of line");
+      if (ai < ac) die("syntax error near `--': rubbish at end of line");
       return (O_EOF);
     }
 
-    /* --- Now look the word up in my table --- */
-
+    /*Now look the word up in my table. */
     sz = strlen(p);
     oo = 0;
     for (o = opttab; o->name; o++) {
       if (strncmp(p, o->name, sz) == 0) {
-       if (strlen(o->name) == sz || ((o->f & OF_SHORT) && sz == 1)) {
-         oo = o;
-         break;
-       }
-       if (oo) {
+       if (strlen(o->name) == sz || ((o->f & OF_SHORT) && sz == 1))
+         { oo = o; break; }
+       if (oo)
          die("ambiguous option name `-%s' (could match `-%s' or `-%s')",
              p, oo->name, o->name);
-       }
        oo = o;
       }
     }
-    if (!oo)
-      die("unrecognized option name `-%s'", p);
-
-    /* --- Sort out the arguments --- */
+    if (!oo) die("unrecognized option name `-%s'", p);
 
+    /* Sort out the arguments. */
     if (ai + oo->nargs > ac)
       die("too few arguments for `-%s' (need %u)", oo->name, oo->nargs);
     ai += oo->nargs;
 
-    /* --- Now process the option --- */
-
+    /* Now process the option. */
     switch (oo->tag) {
-      case O_HELP:
-       help(stdout);
-       exit(0);
-      case O_VERSION:
-       version(stdout);
-       exit(0);
-      case O_USAGE:
-       usage(stdout);
-       exit(0);
-      case O_FILE:
-       file = (*arg)[1];
-       break;
-      default:
-       return (oo->tag);
+      case O_HELP: help(stdout); exit(0);
+      case O_VERSION: version(stdout); exit(0);
+      case O_USAGE: usage(stdout); exit(0);
+      case O_FILE: file = (*arg)[1]; break;
+      default: return (oo->tag);
     }
     continue;
   bad:
@@ -308,6 +268,59 @@ static int n_not(node *nn, const char *p, size_t sz)
   return (!n->arg->func(n->arg, p, sz));
 }
 
+/*----- Other simple node types -------------------------------------------*/
+
+enum { LESS = -1, EQUAL = 0, GREATER = 1 };
+
+typedef struct node_numeric {
+  node n;
+  int dir;
+  int i;
+} node_numeric;
+
+static void parse_numeric(const char *p, int *dir, int *i)
+{
+  long l;
+  const char *pp = p;
+  char *q;
+
+  switch (*pp) {
+    case '-': *dir = LESS; pp++; break;
+    case '+': *dir = GREATER; pp++; break;
+    default: *dir = EQUAL; break;
+  }
+  errno = 0;
+  l = strtol(pp, &q, 0);
+  if (*q || errno || l < INT_MIN || l > INT_MAX)
+    die("bad numeric parameter `%s'", p);
+  *i = l;
+}
+
+static node *make_numeric(const char *const *av,
+                         int (*func)(struct node *, const char *, size_t))
+{
+  node_numeric *n = xmalloc(sizeof(*n));
+  parse_numeric(av[0], &n->dir, &n->i);
+  n->n.func = func;
+  return (&n->n);
+}
+
+static int cmp_numeric(int x, int dir, int n)
+{
+  switch (dir) {
+    case LESS: return (x <= n);
+    case EQUAL: return (x == n);
+    case GREATER: return (x >= n);
+  }
+  abort();
+}
+
+static int n_length(node *nn, const char *p, size_t sz)
+{
+  node_numeric *n = (node_numeric *)nn;
+  return (cmp_numeric(sz, n->dir, n->i));
+}
+
 /*----- Parser for the expression syntax ----------------------------------*/
 
 /* --- A parser context --- */
@@ -331,8 +344,7 @@ static void p_next(p_ctx *p)
 {
   static const char *const eof[] = { "<end>", 0 };
   p->t = nextopt(&p->a);
-  if (p->t == O_EOF)
-    p->a = eof;
+  if (p->t == O_EOF) p->a = eof;
 }
 
 static void p_factor(p_ctx *p, node **nn)
@@ -341,8 +353,7 @@ static void p_factor(p_ctx *p, node **nn)
   if (p->t == O_LPAREN) {
     p_next(p);
     p_expr(p, nn);
-    if (p->t != O_RPAREN)
-      die("syntax error near `%s': missing `)'", *p->a);
+    if (p->t != O_RPAREN) die("syntax error near `%s': missing `)'", *p->a);
     p_next(p);
   } else if (p->t == O_NOT) {
     n = xmalloc(sizeof(node_un));
@@ -358,8 +369,14 @@ static void p_factor(p_ctx *p, node **nn)
       case O_TRACK: *nn = trackword(p->a + 1); break;
 #ifdef HAVE_REGCOMP
       case O_REGEXP: *nn = regexp(p->a + 1); break;
+#endif
+#if defined(HAVE_PCRE) || defined(HAVE_PCRE2)
+      case O_PCRE: *nn = pcrenode(p->a + 1); break;
 #endif
       case O_MONO: *nn = mono(p->a + 1); break;
+      case O_LENGTH: *nn = make_numeric(p->a + 1, n_length); break;
+      case O_LONGEST: *nn = longest(p->a + 1); break;
+      case O_SHORTEST: *nn = shortest(p->a + 1); break;
       default: die("syntax error near `%s': unexpected token", *p->a);
     }
     p_next(p);
@@ -372,14 +389,9 @@ static void p_term(p_ctx *p, node **nn)
   for (;;) {
     p_factor(p, nn);
     switch (p->t) {
-      case O_AND:
-       p_next(p);
-      default:
-       break;
-      case O_RPAREN:
-      case O_OR:
-      case O_EOF:
-       return;
+      case O_AND: p_next(p); break;
+      default: break;
+      case O_RPAREN: case O_OR: case O_EOF: return;
     }
     n = xmalloc(sizeof(node_bin));
     n->left = *nn;
@@ -394,8 +406,7 @@ static void p_expr(p_ctx *p, node **nn)
   node_bin *n;
   for (;;) {
     p_term(p, nn);
-    if (p->t != O_OR)
-      break;
+    if (p->t != O_OR) break;
     p_next(p);
     n = xmalloc(sizeof(node_bin));
     n->left = *nn;
@@ -430,13 +441,44 @@ static node *p_argv(int argc, const char *const argv[])
     exit(EXIT_FAILURE);
   }
   p_expr(&p, &n);
-  if (p.t != O_EOF) {
+  if (p.t != O_EOF)
     die("syntax error near `%s': rubbish at end of line (too many `)'s?)",
        *p.a);
-  }
   return (n);
 }
 
+/*----- At-end stuff ------------------------------------------------------*/
+
+/* --- @atend_register@ --- *
+ *
+ * Arguments:  @int (*func)(void *)@ = function to call
+ *             @void *p@ = handle to pass to it
+ *
+ * Returns:    ---
+ *
+ * Use:                Adds a function to the list of things to do at the end of the
+ *             program.  The function should return nonzero if it produced
+ *             any output.
+ */
+
+typedef struct atend {
+  struct atend *next;
+  int (*func)(void */*p*/);
+  void *p;
+} atend;
+
+static atend *aa_head = 0, **aa_tail = &aa_head;
+
+void atend_register(int (*func)(void */*p*/), void *p)
+{
+  atend *a = xmalloc(sizeof(*a));
+  a->next = 0;
+  a->func = func;
+  a->p = p;
+  *aa_tail = a;
+  aa_tail = &a->next;
+}
+
 /*----- Main code ---------------------------------------------------------*/
 
 /* --- @main@ --- *
@@ -455,7 +497,9 @@ int main(int argc, char *argv[])
   node *n;
   FILE *fp;
   dstr d = DSTR_INIT;
+  int ok = 0;
   char *p, *q, *l;
+  atend *a;
 
   ego(argv[0]);
   n = p_argv(argc, (const char *const *)argv);
@@ -464,12 +508,10 @@ int main(int argc, char *argv[])
     die("error opening `%s': %s", file, strerror(errno));
   for (;;) {
     dstr_reset(&d);
-    if (dstr_putline(&d, fp) < 0)
-      break;
+    if (dstr_putline(&d, fp) < 0) break;
     l = d.buf + d.len;
     for (p = q = d.buf; p < l; p++) {
-      if (!isalnum((unsigned char)*p))
-       continue;
+      if (!isalnum((unsigned char)*p)) continue;
       *q++ = tolower((unsigned char)*p);
     }
     *q = 0;
@@ -477,12 +519,17 @@ int main(int argc, char *argv[])
     if (n->func(n, d.buf, d.len)) {
       fwrite(d.buf, 1, d.len, stdout);
       fputc('\n', stdout);
+      ok = 1;
     }
   }
-  if (!feof(fp))
+  if (ferror(fp) || fclose(fp))
     die("error reading `%s': %s", file, strerror(errno));
-  fclose(fp);
-  return (0);
+  for (a = aa_head; a; a = a->next)
+    if (a->func(a->p)) ok = 1;
+  if (fflush(stdout) || ferror(stdout) || fclose(stdout))
+    die("error writing output: %s", strerror(errno));
+  if (!ok) pquis(stderr, "$: no matches found\n");
+  return (ok ? EX_OK : EX_NONE);
 }
 
 /*----- That's all, folks -------------------------------------------------*/