diff --git a/go/src/lib/regex.go b/go/src/lib/regex.go index 6d4728043..41093faaf 100644 --- a/go/src/lib/regex.go +++ b/go/src/lib/regex.go @@ -4,6 +4,7 @@ package lib // * cst state for captures array // * reset-hook for start of execution // o UTs for that +// * flesh out RegexCaptureBinaryFunctionCallsiteNode to do that import ( "bytes" @@ -125,26 +126,39 @@ func RegexGsubWithCaptures( func RegexMatches(input string, sregex string) (matches bool, capturesOneUp []string) { regex := CompileMillerRegexOrDie(sregex) - stringMatch := regex.MatchString(input) - return stringMatch, nil // TODO + return regexMatchesAux(input, regex) } // ---------------------------------------------------------------- // Package-internal/implementation functions +// xxx: +// $ go run foo.go "ab_cde ab_cde" "(ab)_(cde)" +// MATRIX [][]int{[]int{0, 6, 0, 2, 3, 6}, []int{8, 14, 8, 10, 11, 14}} +// 0 6 "ab_cde" +// n:6 +// 2 0 2 "ab" +// 4 3 6 "cde" +// 8 14 "ab_cde" +// n:6 +// 2 8 10 "ab" +// 4 11 14 "cde" + func regexSubGsubWithCapturesAux( input string, regex *regexp.Regexp, replacement string, breakOnFirst bool, ) string { - matrix := regex.FindAllStringIndex(input, -1) + matrix := regex.FindAllSubmatchIndex([]byte(input), -1) if matrix == nil || len(matrix) == 0 { return input } // xxx instantiate a RegexCaptures object + // xxx update comment for FindAllSubmatchIndex + // The key is the Go library's regex.FindAllStringIndex. It gives us start // (inclusive) and end (exclusive) indices for matches. // @@ -176,8 +190,67 @@ func regexSubGsubWithCapturesAux( return buffer.String() } -// xxx -// echo a=ab_cde | mlrgo --oxtab put ' -// $b = sub($a, "(..)_(...)", "\2-\1"); -// $c = sub($a, "(..)_(.)(..)", ":\1:\2:\3") -// ' +// xxx comment: +// xxx so we early-out +// +// $ go run foo.go "ab_cde ab_cde" "(ab)_(cde)" +// MATRIX [][]int{[]int{0, 6, 0, 2, 3, 6}, []int{8, 14, 8, 10, 11, 14}} +// 0 6 "ab_cde" +// n:6 +// 2 0 2 "ab" +// 4 3 6 "cde" +// 8 14 "ab_cde" +// n:6 +// 2 8 10 "ab" +// 4 11 14 "cde" + +func regexMatchesAux( + input string, + regex *regexp.Regexp, +) (bool, []string) { + matrix := regex.FindAllSubmatchIndex([]byte(input), -1) + if matrix == nil || len(matrix) == 0 { + return false, nil + } + + // 0 is ""; 1..9 for "\1".."\9" + captures := make([]string, 10) + + // xxx update comment for FindAllSubmatchIndex + + // The key is the Go library's regex.FindAllStringIndex. It gives us start + // (inclusive) and end (exclusive) indices for matches. + // + // Example: for pattern "foo" and input "abc foo def foo ghi" we'll have + // matrix [[4 7] [12 15]] which indicates matches from positions 4-6 and + // 12-14. We simply need to concatenate + // * 0-3 "abc " not matching + // * 4-6 "foo" matching + // * 7-11 " def " not matching + // * 12-14 "foo" matching + // * 15-18 " ghi" not matching + // + // Example: with pattern "f.*o" and input "abc foo def foo ghi" we'll have + // matrix [[4 15]] so "foo def foo" will be a matched substring. + + // xxx comment first outer-match only + row := matrix[0] + n := len(row) + if n == 2 { + // xxx comment no captures + return true, nil + } + + i := 1 + for j := 2; j < n; j += 2 { + if i > 9 { + break + } + start := row[j] + end := row[j+1] + captures[i] = input[start:end] + i += 1 + } + + return true, captures +} diff --git a/go/src/lib/regex_test.go b/go/src/lib/regex_test.go index 447408015..ff801e365 100644 --- a/go/src/lib/regex_test.go +++ b/go/src/lib/regex_test.go @@ -39,9 +39,9 @@ var dataForSubWithCaptures = []tDataForSubGsub{ {"abcde", "[a-z]", "X", "Xbcde"}, {"abcde", "[A-Z]", "X", "abcde"}, - //{"ab_cde", "(..)_(...)e", "\\2\\1", "cdeab"}, - //{"ab_cde", "(..)_(...)e", "\\2-\\1", "cde-ab"}, - //{"ab_cde", "(..)_(...)e", "X\\2Y\\1Z", "XcdeYabZ"}, + //{"ab_cde", "(..)_(...)", "\\2\\1", "cdeab"}, + //{"ab_cde", "(..)_(...)", "\\2-\\1", "cde-ab"}, + //{"ab_cde", "(..)_(...)", "X\\2Y\\1Z", "XcdeYabZ"}, } var dataForGsubWithoutCaptures = []tDataForSubGsub{ @@ -64,66 +64,89 @@ var dataForGsubWithCaptures = []tDataForSubGsub{ // xxx needs expected-capture data var dataForMatches = []tDataForMatches{ - {"abcde", "[a-z]", true, nil}, {"abcde", "[A-Z]", false, nil}, + {"abcde", "[a-z]", true, nil}, + {"...ab_cde...", "(..)_(...)", true, []string{"", "ab", "cde", "", "", "", "", "", "", ""}}, } // ---------------------------------------------------------------- func TestRegexSubWithoutCaptures(t *testing.T) { - for _, entry := range dataForSubWithoutCaptures { + for i, entry := range dataForSubWithoutCaptures { actualOutput := RegexSubWithoutCaptures(entry.input, entry.sregex, entry.replacement) if actualOutput != entry.expectedOutput { - t.Fatalf("input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", - entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, + t.Fatalf("case %d input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", + i, entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, ) } } } func TestRegexSubWithCaptures(t *testing.T) { - for _, entry := range dataForSubWithCaptures { + for i, entry := range dataForSubWithCaptures { actualOutput := RegexSubWithCaptures(entry.input, entry.sregex, entry.replacement) if actualOutput != entry.expectedOutput { - t.Fatalf("input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", - entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, + t.Fatalf("case %d input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", + i, entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, ) } } } func TestRegexGsubWithoutCaptures(t *testing.T) { - for _, entry := range dataForGsubWithoutCaptures { + for i, entry := range dataForGsubWithoutCaptures { actualOutput := RegexGsubWithoutCaptures(entry.input, entry.sregex, entry.replacement) if actualOutput != entry.expectedOutput { - t.Fatalf("input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", - entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, + t.Fatalf("case %d input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", + i, entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, ) } } } func TestRegexGsubWithCaptures(t *testing.T) { - for _, entry := range dataForGsubWithCaptures { + for i, entry := range dataForGsubWithCaptures { actualOutput := RegexGsubWithCaptures(entry.input, entry.sregex, entry.replacement) if actualOutput != entry.expectedOutput { - t.Fatalf("input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", - entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, + t.Fatalf("case %d input \"%s\" sregex \"%s\" replacement \"%s\" expected \"%s\" got \"%s\"\n", + i, entry.input, entry.sregex, entry.replacement, entry.expectedOutput, actualOutput, ) } } } func TestRegexMatches(t *testing.T) { - for _, entry := range dataForMatches { + for i, entry := range dataForMatches { actualOutput, actualCaptures := RegexMatches(entry.input, entry.sregex) if actualOutput != entry.expectedOutput { - t.Fatalf("input \"%s\" sregex \"%s\" expected %v got %v\n", - entry.input, entry.sregex, entry.expectedOutput, actualOutput, + t.Fatalf("case %d input \"%s\" sregex \"%s\" expected %v got %v\n", + i, entry.input, entry.sregex, entry.expectedOutput, actualOutput, ) } - // xxx compare actual/expected captures - // xxx make a comparator function - if actualCaptures == nil { + if !compareCaptures(actualCaptures, entry.expectedCaptures) { + t.Fatalf("case %d input \"%s\" sregex \"%s\" expected captures %#v got %#v\n", + i, entry.input, entry.sregex, entry.expectedCaptures, actualCaptures, + ) } } } + +func compareCaptures( + actualCaptures []string, + expectedCaptures []string, +) bool { + if actualCaptures == nil && expectedCaptures == nil { + return true + } + if actualCaptures == nil || expectedCaptures == nil { + return false + } + if len(actualCaptures) != len(expectedCaptures) { + return false + } + for i, _ := range expectedCaptures { + if actualCaptures[i] != expectedCaptures[i] { + return false + } + } + return true +}