chiark / gitweb /
pcre.c, etc.: Support the PCRE2 library.
[anag] / anag.c
diff --git a/anag.c b/anag.c
index 6be2a4e6d3c1c0366314b0b980c4e78476f25b8c..80da3c3267dce37b3b12ec2047ff46588a7686dd 100644 (file)
--- a/anag.c
+++ b/anag.c
@@ -1,13 +1,11 @@
 /* -*-c-*-
- *
- * $Id: anag.c,v 1.5 2002/08/11 12:58:09 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.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"
@@ -89,18 +65,27 @@ The basic tests in the expression are:\n\
 -subgram WORD          matches words which only use letters in WORD\n\
 -wildcard PATTERN      matches with wildcards `*' and `?'\n\
 -trackword WORD                matches words which can be found in a trackword\n\
+-mono PATTERN          matches words isomorphic to the given PATTERN\n\
 "
 #ifdef HAVE_REGCOMP
 "\
 -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 ------------------------------------------------*/
@@ -118,7 +103,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_ANAG, O_SUBG, O_WILD, O_TRACK, O_REGEXP, O_PCRE, O_MONO, O_LENGTH,
+  O_LONGEST, O_SHORTEST,
   O_EOF
 };
 
@@ -142,15 +128,22 @@ 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 },
   { "wildcard",                1,      0,              O_WILD },
   { "trackword",       1,      0,              O_TRACK },
+  { "mono",            1,      0,              O_MONO },
 #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 --- */
 
@@ -303,6 +296,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 --- */
@@ -354,6 +400,13 @@ static void p_factor(p_ctx *p, node **nn)
 #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);
@@ -431,6 +484,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@ --- *
@@ -449,7 +534,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);
@@ -471,12 +558,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 -------------------------------------------------*/