Ara,

> may have a hole, but it think it can be done:

    Yup, this has a hole in cat_and_notsmall:

cat_and_notsmall((?:(?:cat.*^[^s]*$|^[^s]*(?:s(?!mall)[^s]*)+$)|(?:^[^s]*$|^
[^s]*(?:s(?!mall)[^s]*)+$.*cat))+)
========
 cat              matched? true
 small            matched? false
 smallcat         matched? false
 catsmall         matched? false
 smalcat          matched? true
 smalcat small    matched? false
 catsmal          matched? true
 catsmal small    matched? false
 smal             matched? true  <----
 smalsmall        matched? false
 smal small       matched? false
 smallsmal        matched? false
 smalcatsmall     matched? false
 smalcat small    matched? false
 smallcatsmal     matched? false

    If you take a close look at the regular expression, you will see it
breaks down into:

[01]  (?:
[02]    (?:
[03]      cat.*^[^s]*$
[04]      |
[05]      ^[^s]*(?:s(?!mall)[^s]*)+$
[06]    )
[07]    |
[08]    (?:
[09]      ^[^s]*$
[10]      |
[11]      ^[^s]*(?:s(?!mall)[^s]*)+$.*cat
[12]    )
[13]  )+

    Lines [03] and [11] can never match anything (because of the 'cat.*^'
and '$.*cat').  The only reason this regular expression matches anything is
due to [05] and [09].  Since [05] is a more restrictive superset of [09],
this regular expression basically reduces to [05] (i.e. notsmall).

    However, this is not just a case of bad parenthesis, the problem is with
the whole '(something).*(not_something_else)' approach.  This will never
work because the '.*' portion is always able to consume a something_else
before the '(not_something_else)' portion ever sees it.

    The only way I know of to do this is with a
'^(not_something_else)*something(not_something_else)*$' approach.

    I hope this didn't end up sounding like a critique.  It's just that I
couldn't see how this was working until I pulled it apart, and when I did I
found what I thought was interesting stuff.

    - Warren Brown