My magnum opus was a program that factored numbers in an arbitrary amount of time. That is _NOT_ something you hammer out in an afternoon, and without commenting everything thoroughly (like what I was doing, where I was in code execution, what state the whole program was in) I would never have made it.
Non-related story time: At first when playing with brainfuck, I wrote my own BF-interpreter that compiled to an IL which was easier to debug (with my own interpreter that could give me stack traces with comments about where I was in memory and what the hell was going on). After a month, I really didn't need to. My brain had gotten used to reasoning about BF-code and I could actually understand what code was doing as long as I knew which kind of compiler/interpreter it was written for.
regex = "(^[0-9])" //catch the initial digit (group 1)
+ "/" //skip the following slash
+ "([A-Z]+) //capture the identifier (group 2)
...
Regexps are too powerful a tool to ignore. I'd much rather write a simple regex than a whole screenfull of code, especially when the former does the work faster (because regex engines are pretty efficient). def any(x): return x+"*"
def many(x): return x+"+"
def capture(x): return "("+x+")"
def lit(x): return re.escape(x) # shorthand
bol="^"; eol="$"
ident="[A-Za-z_][A-Za-z0-9_]*";
find_function=(bol+any(" ")+lit("def")+many(" ")+
capture(ident)+any(" ")+lit(":")+
any(" ")+eol)
You can handle more or less stuff this way according to how much you and/or your readers like regexp syntaxp. The above is probably further than I'd take it in practice; I'm familiar with the regular expression syntax, but I'd still probably at least use something like the `ident' variable just to keep clutter out of the regexp.(A nice demonstration of this sort of thing is emacs's rx module (see, e.g., http://emacswiki.org/emacs/rx). I couldn't find any good non-emacs documentation about this, nor much that would make sense to people unfamiliar with lisp, but when you're in emacs you can get help on it using C-h f rx RET.)
PRODUCT_ID = "[0-9]{2}[A-Z]{1,5}"
...
CUSTOMER_ID = "[0-9]{2}[A-Z]{1,5}"
...
...
regexp = "(" + PRODUCT_ID + ")" // capture product ID in group 1
+ "something something" // something something
+ "(" + CUSTOMER_ID + ")"; // capture customer ID in group 2
In this example, even though the two constants contain the same regular expresison, they refer to two different concepts. Part of the problem with understanding regexp-based code is connecting parts of the expression with what they mean. Above strategy addresses this.I also like assigning names to capture groups I depend on. So instead of, later in code, asking for e.g. matcher.group(2), I ask for matcher.group(GROUP_CUSTOMER_ID). Makes for a much more readable code.
Speaking of Emacs's rx, it's absolutely amazing and I'm sad that I only discovered it just few days ago :(. Another similar concept, from the other side of "code = data" equality is Common Lisp's (of course it's Lisp again) CL-PPCRE and its internal representation of regular expressions:
* (parse-string "(ab)*")
(:GREEDY-REPETITION 0 NIL (:REGISTER "ab"))
* (parse-string "(a(b))")
(:REGISTER (:SEQUENCE #\a (:REGISTER #\b)))
* (parse-string "(?:abc){3,5}")
(:GREEDY-REPETITION 3 5 (:GROUP "abc"))
You can encode any regexp you want as an S-expression, trading off conciseness for legibility. See http://weitz.de/cl-ppcre/#create-scanner2 for more. "(\.|[^"])*"
You just won't be able to nest them. You're either in a string or you aren't. You're never in a string inside another string.I feel like I gain more than I lose by simply excluding regexps from my toolbelt. (It's a personal choice, I'm not saying it'd be the same for others, since their values may differ from mine.)
Write only regexes are trivial once you have tests: when you don't understand one, throw it off and replace with something you do. Tests pass? Here you go.
Whereas having a whole file of many lines of code is much harder to replace when you don't understand what's it all about.
That makes them pretty maintainable.
Why go to this effort? Because for they're beautiful and a very powerful tool when used for the right problem. Unfortunately they're widely misused (i.e. email validation).
It takes a little more effort, and one does not get the "look how dense it is"-rush, but I guess the same can be said for all code. There is a point where it breaks down, but there is a range of text-matching/extracting problems where regexes are both useful and - if one makes the effort - maintainable.