Rob Pike on regular expressions in lexing and parsing
commandcenter.blogspot.com
commandcenter.blogspot.com
Unfortunately, it will probably be misinterpreted as a general criticism of regular expression systems. There are so many cases where my knowledge of a powerful, general-purpose system has saved me from having to learn a new software library for each random task that I intend to keep using such things as regular expressions, scripting languages, unix command line pipelines, Excel, etc. Writing my own code or learning to use a new, specialized system is Plan B for simple, non-production tasks.
Only when I can amortize the cost (time/effort) over a lot of use (as in a production system), or when the advantage of the specialized system is noticeable to the user (including me), does the specialized system become my Plan A.
General purpose tools, special purpose tools, and custom coding all have different costs and benefits, and there are circumstances under which each one is the best choice.
~jwz
"If you're willing to restrict the flexibility of your approach, you can almost always do something better." — John Carmack
In my experience, the opposite is true (up to the point when your grammar becomes non-regular).
Doing that in a loop seems to be much more difficult. And unless you work on utf-32 data, you have to handle variable-size encoding of characters.
Or maybe I'm also confusing lexers with lexer generators.
Look at http://cstheory.stackexchange.com/questions/448/regular-expr...
Use parsers combinators where you can. There you can put sub-expressions in variables, have meaningful identifiers in your grammar, explorable and debuggable parsing etc.
Actually regex libraries such as PCRE have a good unicode support and are better than me when it comes to do things like matching character properties (letters, uppercase letters, numbers, punctuation, etc).
So i guess the point is that the whole point of a parser/lexer is to look at and validate text so you shouldn't 'outsource' it. but maybe in applications where minor parsing/text validation tasks are more peripheral then regexs are more appropriate?
Definitely. There's a 3-line regex-based tokenizer in one of my side projects (in python). Today, for kicks, I tried writing the same function without regexes using two different approaches. One uses list comprehensions and makes liberal use of python string, set, and list methods, the other is a single-pass ad hoc state machine.
The regex function is 3 lines and parses 1,000,000 characters in 0.3 seconds on my desktop.
The split-strip function is 20 lines, imo very hard to read, and far slower than the regex (1.77 seconds on the same benchmark).
The adhoc state machine is 89 lines and easily the slowest (5.3 seconds on the million byte benchmark.
I'm pretty sure that even if I moved this app towards production I would polish the regex function rather than swap it out entirely with one of the other approaches.
key_sections_regex=re.compile(r'\(([\-+]?[0-9]+,\s*\S+)\):([\s\-0-9]+)', re.S)
def split_keyed_line(keyedline):
return [[k, n.strip()] for k, n in re.findall(key_sections_regex, keyedline)]
def split_keyed_line_splits(keyedline):
whitespace = [' ', '\t', '\n', '\r', '\f', '\v']
notechars=set(list('0123456789-') + whitespace)
offsetchars=set('0123456789-+')
def splitka(ka):
off, mode = ka.split(',')
if set(off).issubset(offsetchars):
if len(set(mode.strip()).intersection(whitespace)) == 0:
return ','.join([off, mode])
raise Exception
def validnotes(notestr):
if notestr.startswith(':'):
if set(notestr[1:]).issubset(notechars):
return notestr[1:].strip()
else:
raise Exception(notestr)
return [[splitka(keyarea), validnotes(notearea.strip())]
for keyarea, notearea in
[x.split(')') for x in keyedline.split('(') if x != '']]
def split_keyed_line_adhoc(keyedline):
whitespace = [' ', '\t', '\n', '\r', '\f', '\v']
digits=list('0123456789')
keyarea_begin = '('
keyarea_end = ')'
keyarea_split = ','
keyarea_offset_signs = ['-', '+']
keyarea_offset_values = digits
notearea_values = digits + whitespace + ['-']
tokens=[]
states=['start', 'offsetsign', 'offset', 'premode',
'mode', 'transition', 'notes', 'accept']
state='start'
current_token=''
subtoken=[]
for ch in keyedline:
if state == 'start':
if ch in whitespace:
continue
if ch == '(':
state = 'offsetsign'
continue
if ch not in whitespace:
print "Syntax Error"
raise Exception
elif state == 'offsetsign':
if ch in keyarea_offset_signs:
current_token += ch
elif ch in keyarea_offset_values:
current_token += ch
else:
print "Syntax Error"
raise Exception
state='offset'
continue
elif state == 'offset':
if ch in keyarea_offset_values:
current_token += ch
elif ch == keyarea_split:
current_token += ch
state='premode'
continue
elif state == 'premode':
if ch in whitespace:
continue
elif ch not in whitespace:
current_token += ch
state = 'mode'
continue
elif state == 'mode':
if ch == keyarea_end:
subtoken.append(current_token)
current_token=''
state='transition'
continue
elif ch in whitespace:
raise Exception
elif ch not in whitespace:
current_token += ch
continue
elif state == 'transition':
if ch == ':':
state='notes'
continue
else:
raise Exception
elif state == 'notes':
if ch in notearea_values:
current_token += ch
continue
elif ch == keyarea_begin:
subtoken.append(current_token.strip())
tokens.append(subtoken)
current_token=''
subtoken=[]
state='offsetsign'
continue
if state == 'notes':
subtoken.append(current_token.strip())
tokens.append(subtoken)
current_token=''
subtoken=[]
current_token=''
state = 'accept'
elif state == 'start':
state = 'accept'
if state == 'accept':
return tokens
else:
return ('error', state, tokens) >>> replace = lambda text, env: ''.join(env[item[1:]] if item.startswith('$') else item[1:] if item.startswith('\\') else item for item in re.findall(r'[^\\$]+|\\.|\$\w+|\\$', text))
>>> print replace(r'This $line has \stuff \\in it that costs \$50 and some $variables.', {'line': 'LINE', 'variables': 'apples'})
This LINE has stuff \in it that costs $50 and some apples.
If I were to write out an explicit loop over the characters of the string, I would be a lot more sure that I wasn't accidentally dropping characters due to an inadvertent failure to make the regexp exhaustive (I originally forgot the \\$ case! Although that reduces to the empty string anyway) and I wouldn't have to forget and rediscover which lexical category each token belonged to.And, although it's not present in this case or in all regexp engines, it's a lot easier to accidentally write an exponential-time algorithm in a regexp than in a nested loop. And my experience has been that it's harder to debug it, too.
"If I were to write out an explicit loop over the characters of the string, I would be a lot more sure that I wasn't accidentally dropping characters due to an inadvertent failure to make the regexp exhaustive"
This is why regex comments exist. For any non-trivial regex (more than a two or three characters), you should break down and document your regex. Otherwise, it's worse than a 1000 character-long perl one-liner.
"it's a lot easier to accidentally write an exponential-time algorithm in a regexp than in a nested loop"
So true. I did not realize it was possible until I made that mistake. Debugging these cases is extremely difficult. For two strings that look similar, the same regex can go crazy on one. But this happened to me only once in the past three years (time when I started to heavily rely on regular expressions for a project).
Regex comments don't help much with inadvertently writing a non-exhaustive regex (i.e. one for which some possible input could fail to match), or a few other kinds of regexp bugs. Or, how would you write the regexp in the above code with comments so that it would be obvious if you left out the \\$ case?
If you are using a scripting language, regular expressions are often faster than a hand-written parser because the regular expression library itself will work at a lowest-level.
Isn't using the lex family of generators pretty 'standard' in terms of writing lexers? Aren't they generally considered fast and in most cases better than hand-written code?
Aren't bad regular expressions just a consequence of someone not properly learning regular expressions? Couldn't the same argument be applied to every programming language?
It was actually quite powerful. But I literally just implemented a state machine. To be honest, I think there were a couple things it did that a regular expression wouldn't be able to do.
However, for the most part, the "efficiency" argument is off base. You can COMPILE regular expressions into tight code.
Rob Pike has written multiple regular expression engines and at least 4 programming languages. I think he understands the efficiency part pretty well.
When I started programming ocaml, I was surprised to find no string primitives for things like split, find, etc... There was only regex. I didn't want to write a regex for doing a simple find or split, so I wrote a string library for my programs. My string library was consistently 4x faster than regex and did only exactly what I wanted.
The regular expression libraries used in scripting languages today (Perl's and PCRE) are optimized C code. A lexer in interpreted code is hardly going to beat them in speed.
Note that a large part of the reason to use scripting languages is the development speed. Regexps FTW, etc.
Edit: I should add -- if I have a problem where I need to parse something complex like a "real" programming language, I go to the LALR libs of course. The right tool for the job. (On second thought -- this is probably what Pike talks about; he doesn't go around solving simple problems, like I do. But no complaints, I've got a job interview tomorrow... :-) )
First -- I noted that I limited my comment to scripting languages.
I did discuss most of the relevant arguments in the article, so I really don't see what your point is?
It doesn't matter for the scripting languages that a regexp lib is large (it is linked in and used anyway), so I ignored that. I also ignored the Unicode point, since it is generally supported in scripting languages' regexp engines.
I did touch on speed and development speed. Shorter code is also generally easier to understand; a simple regexp can often replace 10-20 lines.
In my edit, 10++ hours before your comment, I discussed when full grammars where a better alternative -- so I handled Pike's accusation of treating Regexps as a "panacea for all text processing". (I have used regexps as lexers for grammars. But only for parsers of less than a few hundred lines.)
(I am not going to touch Pike's argument if I and others really grokk regular expressions, because I thought I did understand them until I read "Mastering Regular Expressions". Maybe there are some more satori insights waiting? I do consider myself a little bit familiar with them from automata theory, implementing state engines and usage [Edit: and my considered opinion is that re:s are often a good solution for scripting languages.])