86

Many programmers know the joy of whipping up a quick regular expression, these days often with help of some web service, or more traditionally at interactive prompt, or perhaps writing a small script which has the regular expression under development, and a collection of test cases. In either case the process is iterative and fairly quick: keep hacking at the cryptic-looking string until it matches and captures what you want and will reject what you don't want.

For a simple case result might be something like this, as a Java regexp:

Pattern re = Pattern.compile(
  "^\\s*(?:(?:([\\d]+)\\s*:\\s*)?(?:([\\d]+)\\s*:\\s*))?([\\d]+)(?:\\s*[.,]\\s*([0-9]+))?\\s*$"
);

Many programmers also know the pain of needing to edit a regular expression, or just code around a regular expression in a legacy code base. With a bit editing to split it up, above regexp is still very easy to comprehend for anyone reasonably familiar with regexps, and a regexp veteran should see right away what it does (answer at the end of the post, in case someone wants the exercise of figuring it out themselves).

However, things don't need to get much more complex for a regexp to become truly write-only thing, and even with diligent documentation (which everybody of course does for all complex regexps they write...), modifying the regexps becomes a daunting task. It can be a very dangerous task too, if regexp is not carefully unit tested (but everybody of course has comprehensive unit tests for all their complex regexps, both positive and negative...).

So, long story short, is there a write-read solution/alternative for regular expressions without losing their power? How would the above regexp look like with an alternative approach? Any language is fine, though a multi-language solution would be best, to the degree regexps are multi-language.


And then, what the earlier regexp does is this: parse a string of numbers in format 1:2:3.4, capturing each number, where spaces are allowed and only 3 is required.

15
  • 3
    related thing on SO: stackoverflow.com/a/143636/674039
    – wim
    Apr 15, 2013 at 16:32
  • 28
    Reading / editing regexes is actually trivial if you know what they're supposed to capture. You might have heard of this rarely used feature of most languages called "comments". If you don't put one above a complex regex explaining what it does you'll pay the price later. Also, code review.
    – TC1
    Apr 15, 2013 at 19:08
  • 2
    Two options to clean this up without actually breaking it into smaller pieces. Their presence or absence varies from language to language. (1) extended line regexes, where whitespace in the regex is ignored (unless escaped) and a single-line comment form is added, so you can break it out into logical chunks with indentation, line spacing, and comments. (2) named capture groups, where you can give a name to each parenthetical, which both adds some self-documentation, and automatically populates a hash of matches -- way better than either a numerically indexed array of matches or $N variables.
    – Ben Lee
    Apr 15, 2013 at 20:46
  • 3
    Part of the problem is the regex language itself, and the bad historic choices in its design which are dragged along like baggage. In a sane language, grouping parentheses are purely a syntactic device to shape the parse tree. But in regex implemenations going back to Unix they have semantics: binding registers to subexpression matches. So then you need some more complicated, ugly brackets just to achieve pure grouping!
    – Kaz
    Apr 16, 2013 at 3:51
  • 2
    Not really a practical answer, but it may be useful to mention that the power of regular expression is exactly as that of a finite automaton. That is, regexes can validate/parse the same class of strings validated and parsed by finite automatons. Hence, a human readable representation of a regex should probably be able to quickly build a graph, and I believe most text-based language are really bad at it; that's why we use visual tools for such things. Have a look at hackingoff.com/compilers/regular-expression-to-nfa-dfa to get some inspiration.
    – damix911
    Oct 1, 2015 at 8:31

12 Answers 12

83

A number of people have mentioned composing from smaller parts, but no one's provided an example yet, so here's mine:

string number = "(\\d+)";
string unit = "(?:" + number + "\\s*:\\s*)";
string optionalDecimal = "(?:\\s*[.,]\\s*" + number + ")?";

Pattern re = Pattern.compile(
  "^\\s*(?:" + unit + "?" + unit + ")?" + number + optionalDecimal + "\\s*$"
);

Not the most readable, but I feel like it's clearer than the original.

Also, C# has the @ operator which can be prepended to a string in order to indicate that it is to be taken literally (no escape characters), so number would be @"([\d]+)";

2
  • Just now noticed how both [\\d]+ and [0-9]+ should be just \\d+ (well, some may find [0-9]+ more readable). I'm not going to edit the question, but you may want to fix this answer.
    – hyde
    Apr 17, 2013 at 9:59
  • @hyde - Good catch. Technically they're not quite the same thing - \d will match anything that's considered a number, even in other numbering systems (Chinese, Arabic, etc.), while [0-9] will just match the standard digits. I did standardize on \\d, though, and factored it into the optionalDecimal pattern.
    – Bobson
    Apr 17, 2013 at 13:30
47

The key to documenting the regular expression is documenting it. Far too often people toss in what appears to be line noise and leave it at that.

Within perl a single /x tells the regular expression parser to ignore most whitespace that is neither backslashed nor within a bracketed character class.

The above regular expression would then become:

$re = qr/
  ^\s*
  (?:
    (?:       
      ([\d]+)\s*:\s*
    )?
    (?:
      ([\d]+)\s*:\s*
    )
  )?
  ([\d]+)
  (?:
    \s*[.,]\s*([\d]+)
  )?
  \s*$
/x;

Yes, its a bit consuming of vertical whitespace, though one could shorten it up without sacrificing too much readability.

And then, what the earlier regexp does is this: parse a string of numbers in format 1:2:3.4, capturing each number, where spaces are allowed and only 3 is required.

Looking at this regular expression one can see how it works (and doesn't work). In this case, this regex will match the string 1.

Similar approaches can be taken in other language. The python re.VERBOSE option works there.

Perl6 (the above example was for perl5) takes this further with the concept of rules which leads to even more powerful structures than the PCRE (it provides access to other grammars (context free and context sensitive) than just regular and extended regular ones).

In Java (where this example draws from), one can use string concatenation to form the regex.

Pattern re = Pattern.compile(
  "^\\s*"+
  "(?:"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #1
    ")?"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #2
    ")"+
  ")?"+ // First groups match 0 or 1 times
  "([\\d]+)"+ // Capture group #3
  "(?:\\s*[.,]\\s*([0-9]+))?"+ // Capture group #4 (0 or 1 times)
  "\\s*$"
);

Admittedly, this creates many more " in the string possibly leading to some confusion there, can be more easily read (especially with syntax highlighting on most IDEs) and documented.

The key is recognizing the power and "write once" nature that regular expressions often fall into. Writing the code to defensively avoid this so that the regular expression remains clear and understandable is key. We format Java code for clarity - regular expressions are no different when the language gives you the option to do so.

4
  • 14
    There's a big difference between "documenting" and "adding line breaks".
    – user25946
    Apr 15, 2013 at 19:47
  • 5
    @JonofAllTrades Making the code able to be read is the first step to anything. Adding line breaks also allows one to add comments for that subset of the RE on the same line (something that is more difficult to do on a single long line of regular expression text).
    – user40980
    Apr 15, 2013 at 19:53
  • 3
    @JonofAllTrades, I disagree pretty strongly. "Documenting" and "adding line breaks" are not that different in that they both serve the same purpose -- making the code easier to understand. And for poorly-formatted code, "adding line breaks" serves that purpose much better than adding documentation would.
    – Ben Lee
    Apr 15, 2013 at 20:50
  • 2
    Adding line breaks is a start, but it's about 10% of the job. Other answers give more specifics, which is helpful.
    – user25946
    Apr 15, 2013 at 21:03
29

The "verbose" mode offered by some languages and libraries is one of the answers to these concerns. In this mode, whitespace in regexp string is stripped out (so you need to use \s) and comments are possible. Here's a short example in Python which supports this by default:

email_regex = re.compile(r"""
    ([\w\.\+]+) # username (captured)
    @
    \w+         # minimal viable domain part
    (?:\.w+)    # rest of the domain, after first dot
""", re.VERBOSE)

In any language that doesn't, implementing a translator from verbose to "normal" mode should be a simple task. If you're concerned about your regexps' readability, you would probably justify this time investment pretty easily.

0
16

Every language that uses regexes allows you to compose them from simpler blocks to make reading easier, and with anything more complicated than (or as complicated as) your example, you should definitely take advantage of that option. The particular trouble with Java and many other languages is that they don't treat regular expressions as "first-class" citizens, instead requiring them to sneak into the language via string literals. This means many quotation marks and backslashes that aren't actually part of regex syntax and make things hard to read, and it also means that you can't get much more readable than that without effectively defining your own mini-language and interpreter.

The prototypical better way of integrating regular expressions was of course Perl, with its whitespace option and regex-quoting operators. Perl 6 extends the concept of building up regexes from parts to actual recursive grammars, which is so much better to use it's really no comparison at all. The language may have missed the boat of timeliness, but its regex support was The Good Stuff(tm).

3
  • 1
    By "simpler blocks" mentioned at the start of the answer, do you mean just string concatenation, or something more advanced?
    – hyde
    Apr 15, 2013 at 14:08
  • 7
    I meant defining sub-expressions as shorter string literals, assigning them to local variables with meaningful names, and then concatenating. I find the names are more important to readability than just the layout improvement. Apr 15, 2013 at 14:27
  • Nowadays many languages (like C++, C#, Python) has “raw string” to minimize escape characters. (I noticed that it has been almost a decade, so raw strings might not be available at that time.) Mar 31 at 6:36
13

I like to use Expresso: http://www.ultrapico.com/Expresso.htm

This free application has the following features that I find useful over time:

  • You can simply copy and paste your regex and the application will parse it for you
  • Once your regex is written, you can test it directly from the application (the application will give you the list of captures, replacements...)
  • Once you have tested it, it will generate the C# code to implement it (note that the code will contain the explanations about your regex).

For example, with the regex you just submitted, it would would look like: Sample screen with the initially given regex

Of course, giving it a try is worth a thousand word describing it. Please also note that I'm note related in any way with the editor of this application.

3
  • 4
    would you mind explaining about this in more detail - how and why does it answer the question asked? "Link-only answers" are not quite welcome at Stack Exchange
    – gnat
    Apr 15, 2013 at 15:37
  • 5
    @gnat Sorry about that. You're absolutely right. I hope that my edited answer does provide more insights.
    – E. Jaep
    Apr 15, 2013 at 19:49
  • I can also really recommend: regex101.com
    – Epskampie
    Oct 30, 2019 at 11:42
10

This is an old question and I didn't see any mention of Verbal Expressions so I thought I'd add that information here as well for future seekers. Verbal Expressions were specifically designed to make regex human understandable, without needing to learn the symbol meaning of regex. See the following example. I think this does best what you are asking for.

// Create an example of how to test for correctly formed URLs
var tester = VerEx()
    .startOfLine()
    .then('http')
    .maybe('s')
    .then('://')
    .maybe('www.')
    .anythingBut(' ')
    .endOfLine();

// Create an example URL
var testMe = 'https://www.google.com';

// Use RegExp object's native test() function
if (tester.test(testMe)) {
    alert('We have a correct URL '); // This output will fire}
} else {
    alert('The URL is incorrect');
}

console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/

This example is for javascript, you can find this library now for many of the programming languages.

5
  • 3
    This is awesome! Aug 5, 2017 at 7:48
  • I actually find that harder to understand than: /^ https? "://" "www."? \S* $/ Jun 4, 2020 at 19:40
  • 1
    Absolutely if one has learned regex and practiced enough the more familiar technique will be easier, but take a new programmer that doesn't know regex and then test the results :)
    – Vijay
    Jun 7, 2020 at 9:02
  • This example is easy to understand, but I struggled to figure out how to do certain things with the library, especially because I knew what I wanted to say in "regex." This link should help others: verbalexpressions.github.io/JSVerbalExpressions Apr 23, 2022 at 23:22
  • In practice I've found that if I already have a good regular expression and want to make it more readable by converting it to 'VerbalExpression' code, it is surprisingly difficult if not impossible to generate the same expression. The support for capturing vs non-capturing groups seems especially lacking, and nested capture groups seem tricky. Otherwise I'd be a huge fan of this approach.
    – Adam Wise
    Sep 9, 2022 at 15:20
9

For some things, it might help to just use a grammar like BNF. These can be much easier to read than regular expressions. A tool such as GoldParser Builder can then convert the grammar into a parser that does the heavy lifting for you.

The BNF, EBNF, etc. grammars can be much easier to read and make than a complicated regular expression. GOLD is one tool for such things.

The c2 wiki link below has a list of possible alternatives which can be googled, with some discussion on them included. It is basically a "see also" link to top off my grammar engine recommendation:

Alternatives To Regular Expressions

Taking "alternative" to mean "semantically equivalent facility with different syntax", there are at least these alternatives to/with RegularExpressions:

  • Basic regular expressions
  • "Extended" regular expressions
  • Perl-compatible regular expressions
  • ... and many other variants...
  • SNOBOL-style RE syntax (SnobolLanguage, IconLanguage)
  • SRE syntax (RE's as EssExpressions)
  • different FSM syntaces
  • Finite-state intersection grammars (quite expressive)
  • ParsingExpressionGrammars, as in OMetaLanguage and LuaLanguage (http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html)
  • The parse mode of RebolLanguage
  • ProbabilityBasedParsing...
3
  • would you mind explaining more on what this link does and what it's good for? "Link-only answers" are not quite welcome at Stack Exchange
    – gnat
    Apr 15, 2013 at 18:36
  • 1
    Welcome to Programmers, Nick P. Please ignore the downvote/r, but do read the page on meta that @gnat linked to. Apr 15, 2013 at 22:56
  • @ Christoffer Lette Appreciate your reply. Will try to keep this in mind in future posts. @ gnat Paulo Scardine's comment reflects my posts' intent. The BNF, EBNF, etc. grammars can be much easier to read and make than a complicated regular expression. GOLD is one tool for such things. The c2 link has a list of possible alternatives which can be googled, with some discussion on them included. It was basically a "see also" link to top off my grammar engine recommendation.
    – Nick P
    Apr 16, 2013 at 8:07
4

The simplest way would be to still use regex but build your expression from composing simpler expresssions with descriptive names e.g. http://www.martinfowler.com/bliki/ComposedRegex.html (and yes this is from string concat)

however as an alternative you could also use a parser combinator library e.g. http://jparsec.codehaus.org/ which will give you a full recursive decent parser. again the real power here comes from composition (this time functional composition).

4

I like how Raku has redesigned the regular expression syntax.

It is so much cleaner, extensible, and frankly more powerful.

Here is a direct translation of your regex into a Raku regular expression.

/
    :sigspace

    ^

    [
        [(\d+) ':']?
        [(\d+) ':']
    ]?

    (\d+)

    [
        <[,.]> (\d+)
    ]?

    $
/;

The most noticeable thing is of course the whitespace.
You don't realize how nice adding a few spaces can be until you are prevented from doing so.


Perhaps the second most noticeable in the above translation is :sigspace.

The thing that really makes it noticeable is that I didn't have to write a single \s* even though it works as if I had.

What :sigspace does is translate whitespace in the regular expression into allowing whitespace in the string to be matched.
(Normally whitespace in a regex does nothing.)

Below is a short example of its use.

The first line uses :sigspace.
The second line is the direct translation of what it compiles into.
The third line is the traditional way you would write it.

' 123 456 ' ~~ /:s ^ (\d+) (\d+) $/

' 123 456 ' ~~ /^ <.ws> (\d+) <.ws> (\d+) <.ws> $/

' 123 456 ' ~~ /^  \s*  (\d+)  \s+  (\d+)  \s*  $/

The <ws> token is smart. It knows if whitespace should be optional or mandatory.
Between (\d+) and (\d+) it is mandatory because otherwise you wouldn't be able to tell where one ends and the other starts.
(The . in <.ws> is there to indicate it isn't important enough to be included in the match result object.)


The next thing is that (?:…) has been renamed to […].

In fact every (?SOMETHING … ) is gone.

(?i:  … )  →  [ :i  … ]  or  [ :ignorecase  … ]
(?-i: … )  →  [ :!i … ]  or  [ :!ignorecase … ]

(?<= … )  →  <?after … >
(?<! … )  →  <!after … >

(?=  … )  →  <?before … >
(?!  … )  →  <!before … >

(?> \w+ )  →  \w+:
           →  # see also `:ratchet` mode

(?(condition)yes-regexp|no-regexp)
  →  [ <?{ condition }> yes-regexp || no-regexp ]

(?<named> …)  →  $<named> = …

Note that something like <foo> is a function call.
And <?foo> is a function call where only the truthiness of the result matters.
So then <!foo> must be a function call where only the untruthiness of the result matters.

The (?(condition)yes-regexp|no-regexp) wasn't really replaced with anything. The replacement code is just the combination of features that happen to work together to make adding such a feature uneccesary.

:ignorecase is an adverb that turns on ignorecase mode.
Adverbs are something that are everywhere in Raku.
(It doesn't need to be in [] unless you need to constrain it to a small part of your regex.)


Something which you probably won't notice until it is pointed out, is that regular expressions are code. It is just code with a different default syntax and semantics.

'AAABBBCCC' ~~ /
                   ^

                   $<a> = ('A'+:)

                   {}
                   :my $count = $<a>.chars; # <-- this is a regular variable

                   $<b> = ('B' ** {$count})
                   $<c> = ('C' ** {$count})

                   $
               /

Since it is just code, you can embed or call out to regular Raku code to add or extend any feature you may want.
What that means is all of the weird extensions to regexs that other languages have added are unnecessary.
(They may get added with a nice syntax if they are useful enough.)


For more complex regular expressions, you can create a grammar.

grammar Foo {
  rule TOP {^
    <colon-part> <point-part>?
  }
  rule colon-part {
    <num> ** 1..3 % ':'
    # <num> repeated 1..3 times, each separated by ':'
  }
  rule point-part {
    <[.,]> <num>
  }
  token num { \d+ }
}

say Foo.parse( '1:2:3.4' );
「1:2:3.4」
 colon-part => 「1:2:3」
  num => 「1」
  num => 「2」
  num => 「3」
 point-part => 「.4」
  num => 「4」

Which is roughly the same structure as:

{
  'colon-part' => {
    'num' => [ '1', '2', '3' ],
  }
  'point-part' => {
    'num' => '4',
  }
}

The syntax is such that you could create that same multi-level match data structure using a single regex.

/:s ^
    $<colon-part> = (
        [$<num> = (\d+)] ** 1..3 % ':'
    )
    $<point-part> = (
        <[.,]> $<num> = (\d+)
    )?
    $
/

Note that (…) creates a new scope, while […] doesn't.

You can also combine segments.

my $num = /\d+/;
my $colon-part = /:s <num=$num> ** 1..3 % ':' /;
my $point-part = /:s <[.,]> <num=$num> /;
'1:2:3.4' ~~ /:s ^ <colon-part=$colon-part> <point-part=$point-part>? $/

The reason for <num=$num> is so that the result of the regex in $num gets stored under the name of num in the result object. Otherwise it could have just been <$num>.

You could of course just put the parts of the grammar into the lexical namespace.

my token num {\d+}
my rule colon-part { <num> ** 1..3 % ':' }
my rule point-part {:s <[.,]> <num> }
'1:2:3.4' ~~ /:s ^ <colon-part> <point-part>? $/
3

I thought it'd be worth mentioning logstash's grok expressions. Grok builds upon the idea of composing long parsing expressions from shorter ones. It allows convenient testing of these building blocks and comes prepackaged with over 100 commonly used patterns. Other than these patterns, it allows use of all regular expressions syntax.

The above pattern expressed in grok is (I tested in the debugger app but could have blundered):

"(( *%{NUMBER:a} *:)? *%{NUMBER:b} *:)? *%{NUMBER:c} *(. *%{NUMBER:d} *)?"

The optional parts and spaces make it seem a bit uglier than usual, but both here and in other cases, using grok can make one's life much nicer.

2

In F# you have the FsVerbalExpressions module. It allows you to compose Regexes from verbal expressions, it also has some pre-built regexes (like URL).

One of the examples for this syntax is the following:

let groupName =  "GroupNumber"

VerbEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

If you're not familiar with F# syntax, groupName is the string "GroupNumber".

Then they create a Verbal Expression (VerbEx) which they construct as "COD(?<GroupNumber>[0-9]{3})END". Which they then test on the string "COD123END", where they get the named capture group "GroupNumber". This results in 123.

I honestly find the normal regex much easier to comprehend.

-2

First, understand that code that merely works is bad code. Good code also needs to accurately report any errors encountered.

For example, if you're writing a function to transfer cash from one user's account to another user's account; you wouldn't just return a "worked or failed" boolean because that doesn't give the caller any idea of what went wrong and doesn't allow the caller to inform the user properly. Instead, you might have a set of error codes (or a set of exceptions): couldn't find destination account, insufficient funds in source account, permission denied, can't connect to database, too much load (retry later), etc.

Now think about your "parse a string of numbers in format 1:2:3.4" example. All the regex does is report a "pass/fail" that doesn't allow adequate feedback to be presented to the user (whether this feedback is an error message in a log, or an interactive GUI where the errors are shown in red as the user types, or whatever else). What types of errors does it fail to describe properly? Bad character in first number, first number too large, missing colon after first number, etc.

To convert "bad code that merely works" into "good code that provides adequately descriptive errors" you have to break the regex up into many smaller regexes (typically, regexes that are so small that it's easier to do it without regexes in the first place).

Making the code readable/maintainable is just an accidental consequence of making the code good.

7
  • 6
    Probably not a good assumption. Mine is because A) This doesn't address the question (How to make it readable?), B) Regular expression matching is pass/fail, and if you break it down to the point where you can say exactly why it failed, you lose a lot of power and speed, and increase the complexity, C) There's no indication from the question that there's even the possibility of the match failing - it's simply a question about making the Regex readable. When you have control of the data going in and/or validate it before hand, you can assume it's valid.
    – Bobson
    Apr 19, 2013 at 14:01
  • A) Breaking it into smaller pieces makes it more readable (as a consequence of making it good). C) Where unknown/unvalidated strings enter a piece of software a sane developer would parse (with error reporting) at that point and convert the data into a form that doesn't need reparsing - regex is not needed after that. B) is nonsense that only applies to bad code (refer to points A and C).
    – Brendan
    Apr 20, 2013 at 5:20
  • Going from your C: What if this is his validation logic? The OP's code could be exactly what you're suggesting - validating the input, reporting if it's not valid, and converting it to a usable form (via the captures). All we have is the expression itself. How would you suggest parsing it other than with a regex? If you add some sample code that will accomplish the same result, I'll remove my downvote.
    – Bobson
    Apr 21, 2013 at 15:40
  • If this is "C: Validating (with error reporting)" then it's bad code because the error reporting is bad. If it fails; was it because the string was NULL, or because the first number had too many digits, or because the first separator wasn't :? Imagine a compiler that only had one error message ("ERROR") that was too stupid to tell the user what the problem is. Now imagine thousands of web sites that are just as stupid and display (e.g.) "Bad email address" and nothing more.
    – Brendan
    Apr 22, 2013 at 8:29
  • Also, imagine a half-trained help desk operator getting a bug report from a completely untrained user that says: Software stopped working - last line in software's log is "ERROR: Failed to extract minor version number from version string '1:2-3.4' (expected colon after second number)"
    – Brendan
    Apr 22, 2013 at 8:38

Not the answer you're looking for? Browse other questions tagged or ask your own question.