A Regular Expression Matcher (2007)
cs.princeton.edu
cs.princeton.edu
Rob Pike's simple C regex matcher in Go - https://news.ycombinator.com/item?id=32434412 - Aug 2022 (75 comments)
A Regular Expression Matcher (2007) - https://news.ycombinator.com/item?id=22317138 - Feb 2020 (22 comments)
A Regular Expression Matcher (2007) - https://news.ycombinator.com/item?id=12199836 - Aug 2016 (34 comments)
A Regular Expression Matcher (2007) - https://news.ycombinator.com/item?id=10654150 - Dec 2015 (1 comment)
A regular expression matcher By Rob Pike and Brian Kernighan (2007) - https://news.ycombinator.com/item?id=5672875 - May 2013 (45 comments)
A Regular Expression Matcher in 30 lines of C - https://news.ycombinator.com/item?id=2723366 - July 2011 (9 comments)
While the C++ code on page 101 at https://archive.org/details/practiceofprogra0000kern/page/10... uses a "char c" parameter.
There's also some "unsigned char c" at pages 169 and 170, at https://archive.org/details/practiceofprogra0000kern/page/16... , in C code.
I'm going to guess it's from habit of using "int c" with getchar() to prevent confusion between EOF (-1) and 255. Discussed at https://archive.org/details/practiceofprogra0000kern/page/19... .
That said: it's an implementation which doesn't behave like normal regexps (it's a left-shortest match first instead of the common left-longest) and it's a back-tracker. Because this implementation doesn't allow much choice, expensive examples will look contrived, but extending this implementation with character sets, groups and choices (i.e. a|b) will make its exponential nature felt very quickly.
> extending this implementation
The exercises at the end of the chapter ask the student to extension the implementation along these lines. Including support for utf8.
> exponential nature
Both the essay and the book highlight this issue. The latter comments that some commercial greps (at the time) also had exponential behavior. https://archive.org/details/practiceofprogra0000kern/page/22...
(I didn't choose exactly the same grep sublanguage to implement -- it was something like supporting the most basic escaping instead of ^ and $.)
- Implement the concrete, simplest use case.
- Parameterize first step with function.
- Try to add another use case with another function
- Try to compose 1st and 2nd use case.
Anyone try to bring OOP + Inheritance breaks the simple rules. So composition is the key here.
If we're trying to measure code complexity I think we have to ask "complexity to whom". If it's to humans, we should measure the code as it appears to humans. If it's to machines, then "line" is meaningless and we should look at tokens, the way PG suggested years ago.
Doesn't work for every language, though =P
A tight definition of SLOC isn't something we have. We do have sloccount, or we can install it, different tool from `wc -l` (we can sed the blank lines out first).
Really we should be calculating cyclomatic complexity like adults. I'm told this isn't straightforward! But what is the use of heaven if a man's reach cannot exceed his grasp.
https://www.oilshell.org/archive/Thompson-1968.pdf is longer and doesn't quite fix the problem (see third-to-last paragraph) but it is interesting and very tight.
#include <stdlib.h>
#include <string.h>
int match(char* regex, char* text) {
int n = strlen(regex);
char* active = calloc(n + 1, 1);
char* next_active = calloc(n + 1, 1);
active[0] = 1;
int found = 0;
int anchor = regex[0] == '^';
regex += anchor;
do {
for (int i = 0; i <= n; i++) {
if (!active[i])
continue;
if (regex[i] == '\0') {
found = 1;
goto DONE;
} else if (regex[i + 1] == '*') {
next_active[i + 2] = 1;
if (regex[i] == '.' || regex[i] == *text)
next_active[i] = 1;
} else if (regex[i] == '$' && regex[i + 1] == '\0') {
found = *text == '\0';
goto DONE;
} else {
next_active[i + 1] = regex[i] == *text;
}
}
char* tmp = next_active;
next_active = active;
active = tmp;
active[0] = !anchor;
memset(next_active, 0, n + 1);
} while (*text++ != '\0');
DONE:;
free(active);
free(next_active);
return found;
}Here's my favorite approach to "rediscovering" efficient regex matching: https://semantic-domain.blogspot.com/2013/11/antimirov-deriv... It's implicit in the Thompson paper I linked above, but imo not obvious from it.
You could also devise an iterative solution that wouldn't be more complex - then you simply have to maintain explicit stacks for operators and operands.
definitely a draft