Repository: benhoyt/goawk (go)

Issue:
FS regex [^,]* fails on empty field
For example:

```
$ echo 'x,,z' | goawk -F'[^,]*' '{for (i=1;i<=NF;i++) print i, "<"$i">"}'
1 <>
2 <,>
3 <,>
4 <>

# Should be (and other AWKs print):
$ echo 'x,,z' | gawk -F'[^,]*' '{for (i=1;i<=NF;i++) print i, "<"$i">"}'
1 <>
2 <,,>
3 <>
```

See https://www.austingroupbugs.net/view.php?id=1468 (0001468).

Thanks @raygard for the report.

Pull request:
Fix #190: FS regex [^,]* fails on empty field
This pull request fixes issue #190 by introducing a custom split function that skips empty matches (according to https://www.austingroupbugs.net/view.php?id=1468, this is the correct behaviour). It is used only for FS

Fixes #190

Patch:
diff --git a/interp/io.go b/interp/io.go
index 33659c9..2652ae5 100644
--- a/interp/io.go
+++ b/interp/io.go
@@ -618,6 +618,24 @@ func (p *interp) setLine(line string, isTrueStr bool) {
 	p.reparseCSV = true
 }
 
+// Splits on FS as a regex, appending each field to fields and returning the
+// new slice (for efficiency).
+func (p *interp) splitOnFieldSepRegex(fields []string, line string) []string {
+	indices := p.fieldSepRegex.FindAllStringIndex(line, -1)
+	prevIndex := 0
+	for _, match := range indices {
+		start, end := match[0], match[1]
+		// skip empty matches (https://www.austingroupbugs.net/view.php?id=1468)
+		if start == end {
+			continue
+		}
+		fields = append(fields, line[prevIndex:start])
+		prevIndex = end
+	}
+	fields = append(fields, line[prevIndex:])
+	return fields
+}
+
 // Ensure that the current line is parsed into fields, splitting it
 // into fields if it hasn't been already
 func (p *interp) ensureFields() {
@@ -654,7 +672,7 @@ func (p *interp) ensureFields() {
 		p.fields = strings.Split(p.line, p.fieldSep)
 	default:
 		// Split on FS as a regex
-		p.fields = p.fieldSepRegex.Split(p.line, -1)
+		p.fields = p.splitOnFieldSepRegex(p.fields[:0], p.line)
 	}
 
 	// Special case for when RS=="" and FS is single character,


Test patch:
diff --git a/interp/interp_test.go b/interp/interp_test.go
index 6a8b04e..0409a36 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -65,6 +65,12 @@ NR==2, NR==4 { print $0 }
 NR==3, NR==5 { print NR }
 `, "a\nb\nc\nd\ne\nf\ng", "b\nc\n3\nd\n4\n5\n", "", ""},
 
+	// field separator cannot match empty field
+	{`BEGIN {FS = "[^,]*"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `x,,z`, "1 <>\n2 <,,>\n3 <>\n", "", ""},
+	{`BEGIN {FS = "[^a-z]*"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `xyz`, "1 <xyz>\n", "", ""},
+	{`BEGIN {FS = "[0-9]*"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `xyz`, "1 <xyz>\n", "", ""},
+	{`BEGIN {FS = "(abc)?"} {for (i=1;i<=NF;i++) print i, "<"$i">"}`, `abcdef`, "1 <>\n2 <def>\n", "", ""},
+
 	// print and printf statements
 	{`BEGIN { print "x", "y" }`, "", "x y\n", "", ""},
 	{`BEGIN { print OFS; OFS = ","; print "x", "y" }`, "", " \nx,y\n", "", ""},
@@ -843,9 +849,9 @@ BEGIN { x[1]=3; f5(x); print x[1] }
 	{`BEGIN { print >"out"; close("out"); getline <"out"; print |"out" }  # !awk !gawk`, "", "", "can't write to reader stream", ""},
 
 	// The value of close() on a pipe emulates gawk behavior. Results are identical for both
-	// input and output. Windows does not do POSIX signals.
-	{`BEGIN { cmd="exit 9"; print "" |cmd; print close(cmd) } # !awk !posix`, "", "9\n", "", ""},
-	{`BEGIN { cmd="exec /bin/kill -9 $$"; print "" |cmd; print close(cmd) } # !awk !posix !windows`, "", "265\n", "", ""},
+	// input and output. Windows does not do POSIX signals. Windows gawk uses cmd.exe, not sh.exe.
+	{`BEGIN { cmd="read FOO; exit 9"; print "" |cmd; print close(cmd) } # !awk !posix !windows-gawk`, "", "9\n", "", ""},
+	{`BEGIN { cmd="read FOO; exec /bin/kill -9 $$"; print "" |cmd; print close(cmd) } # !awk !posix !windows`, "", "265\n", "", ""},
 	{`BEGIN { cmd="exit 9"; cmd |getline; print close(cmd) } # !awk !posix`, "", "9\n", "", ""},
 	{`BEGIN { cmd="exec /bin/kill -9 $$"; cmd |getline; print close(cmd) } # !awk !posix !windows`, "", "265\n", "", ""},