Flesh out matches-with-captures

This commit is contained in:
John Kerl 2021-08-08 00:20:16 -04:00
parent 9ea4402933
commit 2c2f7f2291
2 changed files with 126 additions and 30 deletions

View file

@ -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
}

View file

@ -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
}