3 Commits

3 changed files with 60 additions and 6 deletions

View File

@@ -666,10 +666,7 @@ func thompson(re []postfixNode) (Reg, error) {
// nothing), the output of shuntingYard() (and the input to thompson()) ends up being empty.
// In these cases, we will return an NFA with 1 state, with an assertion that is always true.
if len(re) == 0 {
start := newState()
start.content = newContents(EPSILON)
start.isEmpty = true
start.assert = ALWAYS_TRUE
start := zeroLengthMatchState()
nfa = append(nfa, &start)
}
@@ -852,8 +849,30 @@ func thompson(re []postfixNode) (Reg, error) {
s2 := question(s1)
nfa = append(nfa, s2)
case PIPE:
s1 := mustPop(&nfa)
s2 := mustPop(&nfa)
// A pipe operator doesn't actually need either operand to be present. If an operand isn't present,
// it is replaced with an implicit 'matchZeroLength' state (this is the same thing that we add at the top if our
// input has zero postfixNodes).
// Things to think about:
// 'a|'
// '|a'
// '^a|'
// '^|a'
s1, err1 := pop(&nfa)
s2, err2 := pop(&nfa)
if err2 != nil || (s2.groupBegin && len(s2.transitions) == 0) { // Doesn't exist, or its just an LPAREN
if err2 == nil { // Roundabout way of saying that this node existed, but it was an LPAREN, so we append it back
nfa = append(nfa, s2)
}
tmp := zeroLengthMatchState()
s2 = &tmp
}
if err1 != nil || (s1.groupBegin && len(s1.transitions) == 0) { // Doesn't exist, or its just an LPAREN
if err1 == nil { // See above for explanation
nfa = append(nfa, s1)
}
tmp := zeroLengthMatchState()
s1 = &tmp
}
s3 := alternate(s1, s2)
nfa = append(nfa, s3)
}

9
nfa.go
View File

@@ -333,3 +333,12 @@ func newState() State {
ret.output = append(ret.output, &ret)
return ret
}
// Creates and returns a state that _always_ has a zero-length match.
func zeroLengthMatchState() State {
start := newState()
start.content = newContents(EPSILON)
start.isEmpty = true
start.assert = ALWAYS_TRUE
return start
}

View File

@@ -265,6 +265,21 @@ var reTests = []struct {
{`\By\B`, nil, `xyz`, []Group{{1, 2}}},
{`ab|cd`, nil, `abc`, []Group{{0, 2}}},
{`ab|cd`, nil, `abcd`, []Group{{0, 2}, {2, 4}}},
{`$b`, nil, `b`, []Group{}},
{`a\(b`, nil, `a(b`, []Group{{0, 3}}},
{`a\(*b`, nil, `ab`, []Group{{0, 2}}},
{`a\(*b`, nil, `a((b`, []Group{{0, 4}}},
{`a\\b`, nil, `a\b`, []Group{{0, 3}}},
{`a+b+c`, nil, `aabbabc`, []Group{{4, 7}}},
{`)(`, nil, `-`, nil},
{`[^ab]*`, nil, `cde`, []Group{{0, 3}, {3, 3}}},
{`abc`, nil, ``, []Group{}},
{`a*`, nil, ``, []Group{{0, 0}}},
{`a|b|c|d|e`, nil, `e`, []Group{{0, 1}}},
{`abcd*efg`, nil, `abcdefg`, []Group{{0, 7}}},
{`ab*`, nil, `xabyabbbz`, []Group{{1, 3}, {4, 8}}},
{`ab*`, nil, `xayabbbz`, []Group{{1, 2}, {3, 7}}},
{`[abhgefdc]ij`, nil, `hij`, []Group{{0, 3}}},
// Todo - add numeric range tests
}
@@ -294,10 +309,21 @@ var groupTests = []struct {
{"(a?)a?", nil, "ab", []Match{[]Group{{0, 1}, {0, 1}}, []Group{{1, 1}, {1, 1}}, []Group{{2, 2}, {2, 2}}}},
{"(a?)a?", nil, "aa", []Match{[]Group{{0, 2}, {0, 1}}, []Group{{2, 2}, {2, 2}}}},
{"a((b.d){3})", nil, "abfdbhdbid", []Match{[]Group{{0, 10}, {1, 10}, {7, 10}}}},
// Test cases from Python's RE test suite
{`(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\071`, nil, `abcdefghijkl9`, []Match{[]Group{{0, 13}, {0, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}, {6, 7}, {7, 8}, {8, 9}, {9, 10}, {10, 11}, {11, 12}}}},
{`()ef`, nil, `def`, []Match{[]Group{{1, 3}, {1, 1}}}},
{`(?:)ef`, nil, `def`, []Match{[]Group{{1, 3}}}},
{`(?:)`, nil, `def`, []Match{[]Group{{0, 0}}, []Group{{1, 1}}, []Group{{2, 2}}, []Group{{3, 3}}}},
{`((a))`, nil, `abc`, []Match{[]Group{{0, 1}, {0, 1}, {0, 1}}}},
{`(a)b(c)`, nil, `abc`, []Match{[]Group{{0, 3}, {0, 1}, {2, 3}}}},
{`(a+|b)*`, nil, `ab`, []Match{[]Group{{0, 2}, {1, 2}}, []Group{{2, 2}}}},
{`(a+|b)+`, nil, `ab`, []Match{[]Group{{0, 2}, {1, 2}}}},
{`(a+|b)?`, nil, `ab`, []Match{[]Group{{0, 1}, {0, 1}}, []Group{{1, 2}, {1, 2}}, []Group{{2, 2}}}},
{`(a|b|c|d|e)f`, nil, `ef`, []Match{[]Group{{0, 2}, {0, 1}}}},
{`(ab|cd)e`, nil, `abcde`, []Match{[]Group{{2, 5}, {2, 4}}}},
{`^(ab|cd)e`, nil, `abcde`, []Match{}},
{`(abc|)ef`, nil, `abcdef`, []Match{[]Group{{4, 6}, {4, 4}}}},
}
func TestFindAllMatches(t *testing.T) {