Add filtering by length, and retaining only longest/shortest matches.
[anag] / anag.c
diff --git a/anag.c b/anag.c
index 616461c..d4413bd 100644 (file)
--- a/anag.c
+++ b/anag.c
@@ -1,6 +1,6 @@
 /* -*-c-*-
  *
- * $Id: anag.c,v 1.8 2004/04/08 01:36:19 mdw Exp $
+ * $Id$
  *
  * Main driver for anag
  *
@@ -80,6 +80,9 @@ The basic tests in the expression are:\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\
@@ -102,7 +105,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_PCRE, O_MONO,
+  O_ANAG, O_SUBG, O_WILD, O_TRACK, O_REGEXP, O_PCRE, O_MONO, O_LENGTH,
+  O_LONGEST, O_SHORTEST,
   O_EOF
 };
 
@@ -126,7 +130,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 },
@@ -139,6 +143,9 @@ static const struct opt opttab[] = {
 #ifdef HAVE_PCRE
   { "pcre",            1,      0,              O_PCRE },
 #endif
+  { "length",          1,      0,              O_LENGTH },
+  { "longest",                 0,      0,              O_LONGEST },
+  { "shortest",                0,      0,              O_SHORTEST },
 
   /* --- End marker --- */
 
@@ -291,6 +298,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 --- */
@@ -346,6 +406,9 @@ static void p_factor(p_ctx *p, node **nn)
       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);
@@ -423,6 +486,38 @@ static node *p_argv(int argc, const char *const argv[])
   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@ --- *
@@ -441,7 +536,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);
@@ -463,12 +560,19 @@ 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 -------------------------------------------------*/