Repository: benhoyt/goawk (go)

Issue:
Incorrect parsing of complex ++ expressions
Two cases:

1) `echo 2 3 4 | goawk '{ $$0++; print $0 }'` prints `2 4 4` instead of `3`. I think this is happening because we parse this as `($($0))++` rather than `($($0++))`.
2) `BEGIN { $0="3 4 5 6 7 8 9"; a=3; print $$a++++; print }` gives a parsing error instead of printing `7\n3 4 6 6 8 8 9`.

There are two commented-out (TODO) tests in `interp/interp_test.go` for this, as well as a commented-out Gawk test (`parse1`).

Pull request description:
Fix issue #79: Incorrect parsing of complex ++ expressions
As the issue pointed out, there was an error while parsing complex post-increment expressions involving field expressions. The fix on the parser was easy enough. However, while working on the parser, I realized that there was a bug in the compiler too. For example:

`./goawk 'BEGIN { $0="3 4 5 6 7 8 9"; a=3; print ($($a++)++); print $0; print a }'` prints  `7\n3 4 7 6 7 8 9\n3`, whereas

`gawk 'BEGIN { $0="3 4 5 6 7 8 9"; a=3; print ($($a++)++); print $0; print a }'` prints `7\n3 4 6 6 8 8 9\n3`

`./goawk 'BEGIN { n = split("12345", a, ""); i = 1; a[a[a[1]++]++]++; for (i=1;i<=n;i++) printf a[i]; print }` prints `34345\n` whereas

`gawk 'BEGIN { n = split("12345", a, ""); i = 1; a[a[a[1]++]++]++; for (i=1;i<=n;i++) printf a[i]; print }` prints `33345\n`.

This pull request fixes both issues. I uncommented the TODO tests in `interp/interp_test.go` and added one more.

Fixes #79

Patch:
diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go
index 0adf18f..26fc01f 100644
--- a/internal/compiler/compiler.go
+++ b/internal/compiler/compiler.go
@@ -496,9 +496,9 @@ func incrAmount(op lexer.Token) Opcode {
 
 // Generate opcodes for an assignment.
 func (c *compiler) assign(target ast.Expr) {
-	switch target := target.(type) {
+	switch t := target.(type) {
 	case *ast.VarExpr:
-		scope, index := c.scalarInfo(target.Name)
+		scope, index := c.scalarInfo(t.Name)
 		switch scope {
 		case resolver.Global:
 			c.add(AssignGlobal, opcodeInt(index))
@@ -508,17 +508,36 @@ func (c *compiler) assign(target ast.Expr) {
 			c.add(AssignSpecial, opcodeInt(index))
 		}
 	case *ast.FieldExpr:
-		c.expr(target.Index)
+		c.expr(t.Index)
 		c.add(AssignField)
 	case *ast.IndexExpr:
-		c.index(target.Index)
-		scope, index := c.arrayInfo(target.Array)
-		switch scope {
-		case resolver.Global:
-			c.add(AssignArrayGlobal, opcodeInt(index))
-		case resolver.Local:
-			c.add(AssignArrayLocal, opcodeInt(index))
-		}
+		c.index(t.Index)
+		c.assignIndexExpr(t)
+	}
+}
+
+func (c *compiler) assignIndexExpr(target *ast.IndexExpr) {
+	scope, index := c.arrayInfo(target.Array)
+	switch scope {
+	case resolver.Global:
+		c.add(AssignArrayGlobal, opcodeInt(index))
+	case resolver.Local:
+		c.add(AssignArrayLocal, opcodeInt(index))
+	}
+}
+
+// Assign to target, but instead of evaluating the index, rotate it to the top
+// of the stack first (for applicable target types).
+func (c *compiler) assignRoteIndex(target ast.Expr) {
+	switch t := target.(type) {
+	case *ast.VarExpr:
+		c.assign(target) // no index for VarExpr, just call assign
+	case *ast.FieldExpr:
+		c.add(Rote)
+		c.add(AssignField)
+	case *ast.IndexExpr:
+		c.add(Rote)
+		c.assignIndexExpr(t)
 	}
 }
 
@@ -706,19 +725,20 @@ func (c *compiler) expr(expr ast.Expr) {
 			op = Subtract
 		}
 		if e.Pre {
-			c.expr(e.Expr)
+			c.dupeIndexLValue(e.Expr)
 			c.expr(&ast.NumExpr{1})
 			c.add(op)
 			c.add(Dupe)
+			c.assignRoteIndex(e.Expr)
 		} else {
-			c.expr(e.Expr)
-			c.expr(&ast.NumExpr{0})
+			c.dupeIndexLValue(e.Expr)
+			c.expr(&ast.NumExpr{0}) // add 0 to coerce result to number
 			c.add(Add)
 			c.add(Dupe)
 			c.expr(&ast.NumExpr{1})
 			c.add(op)
+			c.assignRoteIndex(e.Expr)
 		}
-		c.assign(e.Expr)
 
 	case *ast.AssignExpr:
 		// Most AssignExpr (standalone) will be handled by the ExprStmt special case
@@ -728,12 +748,22 @@ func (c *compiler) expr(expr ast.Expr) {
 
 	case *ast.AugAssignExpr:
 		// Most AugAssignExpr (standalone) will be handled by the ExprStmt special case
-		c.expr(e.Right)
-		c.expr(e.Left)
-		c.add(Swap)
-		c.binaryOp(e.Op)
-		c.add(Dupe)
-		c.assign(e.Left)
+		switch e.Left.(type) {
+		case *ast.FieldExpr, *ast.IndexExpr:
+			c.expr(e.Right)
+			c.dupeIndexLValue(e.Left)
+			c.add(Rote)
+			c.binaryOp(e.Op)
+			c.add(Dupe)
+			c.assignRoteIndex(e.Left)
+		case *ast.VarExpr:
+			c.expr(e.Right)
+			c.expr(e.Left)
+			c.add(Swap)
+			c.binaryOp(e.Op)
+			c.add(Dupe)
+			c.assign(e.Left)
+		}
 
 	case *ast.CondExpr:
 		jump := c.condition(e.Cond, true)
@@ -746,13 +776,7 @@ func (c *compiler) expr(expr ast.Expr) {
 
 	case *ast.IndexExpr:
 		c.index(e.Index)
-		scope, index := c.arrayInfo(e.Array)
-		switch scope {
-		case resolver.Global:
-			c.add(ArrayGlobal, opcodeInt(index))
-		case resolver.Local:
-			c.add(ArrayLocal, opcodeInt(index))
-		}
+		c.indexExpr(e)
 
 	case *ast.CallExpr:
 		// split and sub/gsub require special cases as they have lvalue arguments
@@ -777,12 +801,23 @@ func (c *compiler) expr(expr ast.Expr) {
 			if len(e.Args) == 3 {
 				target = e.Args[2]
 			}
-			c.expr(e.Args[0])
-			c.expr(e.Args[1])
-			c.expr(target)
-			c.add(CallBuiltin, Opcode(op))
-			c.assign(target)
+			switch target.(type) {
+			case *ast.FieldExpr, *ast.IndexExpr:
+				c.dupeIndexLValue(target)
+				c.expr(e.Args[0])
+				c.expr(e.Args[1])
+				c.add(Rote)
+				c.add(CallBuiltin, Opcode(op))
+				c.assignRoteIndex(target)
+			case *ast.VarExpr:
+				c.expr(e.Args[0])
+				c.expr(e.Args[1])
+				c.expr(target)
+				c.add(CallBuiltin, Opcode(op))
+				c.assign(target)
+			}
 			return
+
 		case lexer.F_LENGTH:
 			if len(e.Args) > 0 {
 				// Determine if the call is length(arrayVar) or length(stringExpr).
@@ -950,6 +985,33 @@ func (c *compiler) expr(expr ast.Expr) {
 	}
 }
 
+func (c *compiler) indexExpr(e *ast.IndexExpr) {
+	scope, index := c.arrayInfo(e.Array)
+	switch scope {
+	case resolver.Global:
+		c.add(ArrayGlobal, opcodeInt(index))
+	case resolver.Local:
+		c.add(ArrayLocal, opcodeInt(index))
+	}
+}
+
+// Compile an lvalue expression, but Dupe the index for applicable expr types
+// so it can be used later for assignIndexExpr (without evaluating it again).
+func (c *compiler) dupeIndexLValue(expr ast.Expr) {
+	switch e := expr.(type) {
+	case *ast.VarExpr:
+		c.expr(expr) // VarExpr has no index, so Dupe is not needed
+	case *ast.FieldExpr:
+		c.expr(e.Index)
+		c.add(Dupe)
+		c.add(Field)
+	case *ast.IndexExpr:
+		c.index(e.Index)
+		c.add(Dupe)
+		c.indexExpr(e)
+	}
+}
+
 // Generate a Concat opcode or, if possible, compact multiple Concats into one
 // ConcatMulti opcode.
 func (c *compiler) concatOp(expr *ast.BinaryExpr) {
diff --git a/internal/compiler/opcode_string.go b/internal/compiler/opcode_string.go
index 6929e48..030e8b3 100644
--- a/internal/compiler/opcode_string.go
+++ b/internal/compiler/opcode_string.go
@@ -14,98 +14,99 @@ func _() {
 	_ = x[Dupe-3]
 	_ = x[Drop-4]
 	_ = x[Swap-5]
-	_ = x[Field-6]
-	_ = x[FieldInt-7]
-	_ = x[FieldByName-8]
-	_ = x[FieldByNameStr-9]
-	_ = x[Global-10]
-	_ = x[Local-11]
-	_ = x[Special-12]
-	_ = x[ArrayGlobal-13]
-	_ = x[ArrayLocal-14]
-	_ = x[InGlobal-15]
-	_ = x[InLocal-16]
-	_ = x[AssignField-17]
-	_ = x[AssignGlobal-18]
-	_ = x[AssignLocal-19]
-	_ = x[AssignSpecial-20]
-	_ = x[AssignArrayGlobal-21]
-	_ = x[AssignArrayLocal-22]
-	_ = x[Delete-23]
-	_ = x[DeleteAll-24]
-	_ = x[IncrField-25]
-	_ = x[IncrGlobal-26]
-	_ = x[IncrLocal-27]
-	_ = x[IncrSpecial-28]
-	_ = x[IncrArrayGlobal-29]
-	_ = x[IncrArrayLocal-30]
-	_ = x[AugAssignField-31]
-	_ = x[AugAssignGlobal-32]
-	_ = x[AugAssignLocal-33]
-	_ = x[AugAssignSpecial-34]
-	_ = x[AugAssignArrayGlobal-35]
-	_ = x[AugAssignArrayLocal-36]
-	_ = x[Regex-37]
-	_ = x[IndexMulti-38]
-	_ = x[ConcatMulti-39]
-	_ = x[Add-40]
-	_ = x[Subtract-41]
-	_ = x[Multiply-42]
-	_ = x[Divide-43]
-	_ = x[Power-44]
-	_ = x[Modulo-45]
-	_ = x[Equals-46]
-	_ = x[NotEquals-47]
-	_ = x[Less-48]
-	_ = x[Greater-49]
-	_ = x[LessOrEqual-50]
-	_ = x[GreaterOrEqual-51]
-	_ = x[Concat-52]
-	_ = x[Match-53]
-	_ = x[NotMatch-54]
-	_ = x[Not-55]
-	_ = x[UnaryMinus-56]
-	_ = x[UnaryPlus-57]
-	_ = x[Boolean-58]
-	_ = x[Jump-59]
-	_ = x[JumpFalse-60]
-	_ = x[JumpTrue-61]
-	_ = x[JumpEquals-62]
-	_ = x[JumpNotEquals-63]
-	_ = x[JumpLess-64]
-	_ = x[JumpGreater-65]
-	_ = x[JumpLessOrEqual-66]
-	_ = x[JumpGreaterOrEqual-67]
-	_ = x[Next-68]
-	_ = x[Nextfile-69]
-	_ = x[Exit-70]
-	_ = x[ExitStatus-71]
-	_ = x[ForIn-72]
-	_ = x[BreakForIn-73]
-	_ = x[CallBuiltin-74]
-	_ = x[CallLengthArray-75]
-	_ = x[CallSplit-76]
-	_ = x[CallSplitSep-77]
-	_ = x[CallSprintf-78]
-	_ = x[CallUser-79]
-	_ = x[CallNative-80]
-	_ = x[Return-81]
-	_ = x[ReturnNull-82]
-	_ = x[Nulls-83]
-	_ = x[Print-84]
-	_ = x[Printf-85]
-	_ = x[Getline-86]
-	_ = x[GetlineField-87]
-	_ = x[GetlineGlobal-88]
-	_ = x[GetlineLocal-89]
-	_ = x[GetlineSpecial-90]
-	_ = x[GetlineArray-91]
-	_ = x[EndOpcode-92]
+	_ = x[Rote-6]
+	_ = x[Field-7]
+	_ = x[FieldInt-8]
+	_ = x[FieldByName-9]
+	_ = x[FieldByNameStr-10]
+	_ = x[Global-11]
+	_ = x[Local-12]
+	_ = x[Special-13]
+	_ = x[ArrayGlobal-14]
+	_ = x[ArrayLocal-15]
+	_ = x[InGlobal-16]
+	_ = x[InLocal-17]
+	_ = x[AssignField-18]
+	_ = x[AssignGlobal-19]
+	_ = x[AssignLocal-20]
+	_ = x[AssignSpecial-21]
+	_ = x[AssignArrayGlobal-22]
+	_ = x[AssignArrayLocal-23]
+	_ = x[Delete-24]
+	_ = x[DeleteAll-25]
+	_ = x[IncrField-26]
+	_ = x[IncrGlobal-27]
+	_ = x[IncrLocal-28]
+	_ = x[IncrSpecial-29]
+	_ = x[IncrArrayGlobal-30]
+	_ = x[IncrArrayLocal-31]
+	_ = x[AugAssignField-32]
+	_ = x[AugAssignGlobal-33]
+	_ = x[AugAssignLocal-34]
+	_ = x[AugAssignSpecial-35]
+	_ = x[AugAssignArrayGlobal-36]
+	_ = x[AugAssignArrayLocal-37]
+	_ = x[Regex-38]
+	_ = x[IndexMulti-39]
+	_ = x[ConcatMulti-40]
+	_ = x[Add-41]
+	_ = x[Subtract-42]
+	_ = x[Multiply-43]
+	_ = x[Divide-44]
+	_ = x[Power-45]
+	_ = x[Modulo-46]
+	_ = x[Equals-47]
+	_ = x[NotEquals-48]
+	_ = x[Less-49]
+	_ = x[Greater-50]
+	_ = x[LessOrEqual-51]
+	_ = x[GreaterOrEqual-52]
+	_ = x[Concat-53]
+	_ = x[Match-54]
+	_ = x[NotMatch-55]
+	_ = x[Not-56]
+	_ = x[UnaryMinus-57]
+	_ = x[UnaryPlus-58]
+	_ = x[Boolean-59]
+	_ = x[Jump-60]
+	_ = x[JumpFalse-61]
+	_ = x[JumpTrue-62]
+	_ = x[JumpEquals-63]
+	_ = x[JumpNotEquals-64]
+	_ = x[JumpLess-65]
+	_ = x[JumpGreater-66]
+	_ = x[JumpLessOrEqual-67]
+	_ = x[JumpGreaterOrEqual-68]
+	_ = x[Next-69]
+	_ = x[Nextfile-70]
+	_ = x[Exit-71]
+	_ = x[ExitStatus-72]
+	_ = x[ForIn-73]
+	_ = x[BreakForIn-74]
+	_ = x[CallBuiltin-75]
+	_ = x[CallLengthArray-76]
+	_ = x[CallSplit-77]
+	_ = x[CallSplitSep-78]
+	_ = x[CallSprintf-79]
+	_ = x[CallUser-80]
+	_ = x[CallNative-81]
+	_ = x[Return-82]
+	_ = x[ReturnNull-83]
+	_ = x[Nulls-84]
+	_ = x[Print-85]
+	_ = x[Printf-86]
+	_ = x[Getline-87]
+	_ = x[GetlineField-88]
+	_ = x[GetlineGlobal-89]
+	_ = x[GetlineLocal-90]
+	_ = x[GetlineSpecial-91]
+	_ = x[GetlineArray-92]
+	_ = x[EndOpcode-93]
 }
 
-const _Opcode_name = "NopNumStrDupeDropSwapFieldFieldIntFieldByNameFieldByNameStrGlobalLocalSpecialArrayGlobalArrayLocalInGlobalInLocalAssignFieldAssignGlobalAssignLocalAssignSpecialAssignArrayGlobalAssignArrayLocalDeleteDeleteAllIncrFieldIncrGlobalIncrLocalIncrSpecialIncrArrayGlobalIncrArrayLocalAugAssignFieldAugAssignGlobalAugAssignLocalAugAssignSpecialAugAssignArrayGlobalAugAssignArrayLocalRegexIndexMultiConcatMultiAddSubtractMultiplyDividePowerModuloEqualsNotEqualsLessGreaterLessOrEqualGreaterOrEqualConcatMatchNotMatchNotUnaryMinusUnaryPlusBooleanJumpJumpFalseJumpTrueJumpEqualsJumpNotEqualsJumpLessJumpGreaterJumpLessOrEqualJumpGreaterOrEqualNextNextfileExitExitStatusForInBreakForInCallBuiltinCallLengthArrayCallSplitCallSplitSepCallSprintfCallUserCallNativeReturnReturnNullNullsPrintPrintfGetlineGetlineFieldGetlineGlobalGetlineLocalGetlineSpecialGetlineArrayEndOpcode"
+const _Opcode_name = "NopNumStrDupeDropSwapRoteFieldFieldIntFieldByNameFieldByNameStrGlobalLocalSpecialArrayGlobalArrayLocalInGlobalInLocalAssignFieldAssignGlobalAssignLocalAssignSpecialAssignArrayGlobalAssignArrayLocalDeleteDeleteAllIncrFieldIncrGlobalIncrLocalIncrSpecialIncrArrayGlobalIncrArrayLocalAugAssignFieldAugAssignGlobalAugAssignLocalAugAssignSpecialAugAssignArrayGlobalAugAssignArrayLocalRegexIndexMultiConcatMultiAddSubtractMultiplyDividePowerModuloEqualsNotEqualsLessGreaterLessOrEqualGreaterOrEqualConcatMatchNotMatchNotUnaryMinusUnaryPlusBooleanJumpJumpFalseJumpTrueJumpEqualsJumpNotEqualsJumpLessJumpGreaterJumpLessOrEqualJumpGreaterOrEqualNextNextfileExitExitStatusForInBreakForInCallBuiltinCallLengthArrayCallSplitCallSplitSepCallSprintfCallUserCallNativeReturnReturnNullNullsPrintPrintfGetlineGetlineFieldGetlineGlobalGetlineLocalGetlineSpecialGetlineArrayEndOpcode"
 
-var _Opcode_index = [...]uint16{0, 3, 6, 9, 13, 17, 21, 26, 34, 45, 59, 65, 70, 77, 88, 98, 106, 113, 124, 136, 147, 160, 177, 193, 199, 208, 217, 227, 236, 247, 262, 276, 290, 305, 319, 335, 355, 374, 379, 389, 400, 403, 411, 419, 425, 430, 436, 442, 451, 455, 462, 473, 487, 493, 498, 506, 509, 519, 528, 535, 539, 548, 556, 566, 579, 587, 598, 613, 631, 635, 643, 647, 657, 662, 672, 683, 698, 707, 719, 730, 738, 748, 754, 764, 769, 774, 780, 787, 799, 812, 824, 838, 850, 859}
+var _Opcode_index = [...]uint16{0, 3, 6, 9, 13, 17, 21, 25, 30, 38, 49, 63, 69, 74, 81, 92, 102, 110, 117, 128, 140, 151, 164, 181, 197, 203, 212, 221, 231, 240, 251, 266, 280, 294, 309, 323, 339, 359, 378, 383, 393, 404, 407, 415, 423, 429, 434, 440, 446, 455, 459, 466, 477, 491, 497, 502, 510, 513, 523, 532, 539, 543, 552, 560, 570, 583, 591, 602, 617, 635, 639, 647, 651, 661, 666, 676, 687, 702, 711, 723, 734, 742, 752, 758, 768, 773, 778, 784, 791, 803, 816, 828, 842, 854, 863}
 
 func (i Opcode) String() string {
 	if i < 0 || i >= Opcode(len(_Opcode_index)-1) {
diff --git a/internal/compiler/opcodes.go b/internal/compiler/opcodes.go
index 4dca3dd..da9ee6c 100644
--- a/internal/compiler/opcodes.go
+++ b/internal/compiler/opcodes.go
@@ -22,6 +22,7 @@ const (
 	Dupe
 	Drop
 	Swap
+	Rote
 
 	// Fetch a field, variable, or array item
 	Field
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,
diff --git a/interp/vm.go b/interp/vm.go
index 0ba778a..e531afb 100644
--- a/interp/vm.go
+++ b/interp/vm.go
@@ -60,6 +60,11 @@ func (p *interp) execute(code []compiler.Opcode) error {
 			l, r := p.peekTwo()
 			p.replaceTwo(r, l)
 
+		case compiler.Rote:
+			s := p.peekSlice(3)
+			v0, v1, v2 := s[0], s[1], s[2]
+			s[0], s[1], s[2] = v1, v2, v0
+
 		case compiler.Field:
 			index := p.peekTop()
 			v := p.getField(int(index.num()))
diff --git a/parser/parser.go b/parser/parser.go
index 3ffcdfc..33b1e5f 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -733,7 +733,16 @@ func (p *parser) primary() ast.Expr {
 		return &ast.RegExpr{regex}
 	case DOLLAR:
 		p.next()
-		return &ast.FieldExpr{p.primary()}
+		var expr ast.Expr = &ast.FieldExpr{p.primary()}
+		// Post-increment operators have lower precedence than primary
+		// expressions by default, except for field expressions with
+		// post-increments (e.g., $$1++ = $($1++), NOT $($1)++).
+		if p.tok == INCR || p.tok == DECR {
+			op := p.tok
+			p.next()
+			expr = &ast.IncrExpr{expr, op, false}
+		}
+		return expr
 	case AT:
 		p.next()
 		return &ast.NamedFieldExpr{p.primary()}


Test patch:
diff --git a/goawk_test.go b/goawk_test.go
index e864056..77679a8 100644
--- a/goawk_test.go
+++ b/goawk_test.go
@@ -232,9 +232,8 @@ func TestGAWK(t *testing.T) {
 		"getline":  true, // getline syntax issues (may be okay, see grammar notes at http://pubs.opengroup.org/onlinepubs/007904975/utilities/awk.html#tag_04_06_13_14)
 		"getline3": true, // getline syntax issues (similar to above)
 
-		"gsubtst7":     true, // something wrong with gsub or field split/join
-		"splitwht":     true, // other awks handle split(s, a, " ") differently from split(s, a, / /)
-		"parse1": true, // incorrect parsing of $$a++++ (see TODOs in interp_test.go too)
+		"gsubtst7": true, // something wrong with gsub or field split/join
+		"splitwht": true, // other awks handle split(s, a, " ") differently from split(s, a, / /)
 
 		"rscompat": true, // GoAWK allows multi-char RS by default
 		"rsstart2": true, // GoAWK ^ and $ anchors match beginning and end of line, not file (unlike Gawk)
diff --git a/interp/fuzz_test.go b/interp/fuzz_test.go
index 8bdbbbb..9ebc624 100644
--- a/interp/fuzz_test.go
+++ b/interp/fuzz_test.go
@@ -35,7 +35,7 @@ func FuzzSource(f *testing.F) {
 		}
 		interpreter, err := interp.New(prog)
 		if err != nil {
-			f.Fatalf("interp.New error: %v", err)
+			t.Fatalf("interp.New error: %v", err)
 		}
 		config := interp.Config{
 			Stdin:        strings.NewReader("foo bar\nbazz\n"),
diff --git a/interp/interp_test.go b/interp/interp_test.go
index 6a8b04e..a2208b9 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", "", ""},
@@ -422,9 +428,25 @@ BEGIN {
 	{`BEGIN { $1234567=1 }`, "", "", "field index too large: 1234567", ""},
 	{`0 in FS  # !awk - doesn't flag this as an error`, "x", "",
 		`parse error at 1:6: can't use scalar "FS" as array`, "array"},
-	// TODO: I think this is happening because we parse this as ($($0))++ rather than ($($0++))
-	// {`{ $$0++; print $0 }`, "2 3 4", "3\n", "", ""},
-	// {`BEGIN { $0="3 4 5 6 7 8 9"; a=3; print $$a++++; print }`, "", "7\n3 4 6 6 8 8 9\n", "", ""},
+	{`{ $$0++; print $0 }`, "2 3 4", "3\n", "", ""},
+	{`BEGIN { $0="3 4 5 6 7 8 9"; a=3; print $$a++++; print }`, "", "7\n3 4 6 6 8 8 9\n", "", ""},
+	{`BEGIN { n = split("12345", a, ""); i = 1; a[a[a[1]++]++]++; for (i=1;i<=n;i++) printf a[i]; print }`, "", "33345\n", "", ""},
+	{`
+BEGIN {
+    a[1] = "ciao"
+    a[2] = "."
+    a[3] = ".."
+    print gsub("ciao", "hello", a[gsub(".", ",", a[gsub(".", ",", a[f()])])])
+    print a[1]
+    print a[2]
+    print a[3]
+}
+
+function f() {
+    print "f"
+    return 3
+}
+`, "", "f\n1\nhello\n,\n,,\n", "", ""},
 
 	// Lots of NF tests with different combinations of NF, $, and number
 	// of input fields. Some of these cause segmentation faults on awk
@@ -482,6 +504,12 @@ BEGIN {
 	{`BEGIN { if (0||x+=2) print "t", x }`, "", "t 2\n", "", ""},
 	{`BEGIN { print(1&&x=2, 1||x=2, 1~x=2, 1!~x=2, 1==x=2, 1!=x=2, 1<x=2, 1<=x=2, 1>x=2, 1>=x=2); print x }`,
 		"", "1 1 0 1 0 1 1 1 0 0\n2\n", "", ""},
+	{`BEGIN { print a[f()]+=g(); print a["x"] }  function f() { print "f"; return "x" }  function g() { print "g"; return 1 }`,
+		"", "g\nf\n1\n1\n", "", ""},
+	{`{ print $f()+=g(); print }  function f() { print "f"; return 2 }  function g() { print "g"; return 1 }`,
+		"1 2 3", "g\nf\n3\n1 3 3\n", "", ""},
+	{`BEGIN { x += x++; print x }`, "", "1\n", "", ""},
+	{`BEGIN { print(x += x++); print x }`, "", "1\n1\n", "", ""},
 
 	// Incr/decr expressions
 	{`BEGIN { print x++; print x }`, "", "0\n1\n", "", ""},
@@ -499,6 +527,14 @@ BEGIN {
 	{"{ print $++n; print $--n }", "x y", "x\nx y\n", "", ""},
 	{"{ print $++a }", "1 2 3\na b c\nd e f\n", "1\nb\nf\n", "", ""},
 	{"BEGIN{ a = 3; b = 7; c = (a)++b; print a, b, c }", "", "3 8 38\n", "", ""},
+	{`BEGIN { print ++a[f()]; print a["x"] }  function f() { print "f"; return "x" }`,
+		"", "f\n1\n1\n", "", ""},
+	{`BEGIN { print a[f()]++; print a["x"] }  function f() { print "f"; return "x" }`,
+		"", "f\n0\n1\n", "", ""},
+	{`{ print ++$f(); print }  function f() { print "f"; return 2 }`,
+		"1 2 3", "f\n3\n1 3 3\n", "", ""},
+	{`{ print $f()++; print }  function f() { print "f"; return 2 }`,
+		"1 2 3", "f\n2\n1 3 3\n", "", ""},
 
 	// Builtin functions
 	{`BEGIN { print sin(0), sin(0.5), sin(1), sin(-1) }`, "", "0 0.479426 0.841471 -0.841471\n", "", ""},
@@ -550,6 +586,10 @@ BEGIN {
 	{`BEGIN { n = split("1 2", a); print (n, a[1], a[2], a[1]==1, a[2]==2) }`, "", "2 1 2 1 1\n", "", ""},
 	{`BEGIN { x = "1.2.3"; print sub(/\./, ",", x); print x }`, "", "1\n1,2.3\n", "", ""},
 	{`BEGIN { x = "1.2.3"; print sub(/\./, ",\\", x); print x }`, "", "1\n1,\\2.3\n", "", ""},
+	{`BEGIN { a["x"] = "1.2.3"; print sub(/\./, ",", a[f()]); print a["x"] }  function f() { print "f"; return "x" }`,
+		"", "f\n1\n1,2.3\n", "", ""},
+	{`{ print sub(/\./, ",", $f()); print }  function f() { print "f"; return 2 }`,
+		"x 1.2.3 y", "f\n1\nx 1,2.3 y\n", "", ""},
 	{`{ print sub(/\./, ","); print $0 }`, "1.2.3", "1\n1,2.3\n", "", ""},
 	{`BEGIN { x = "1.2.3"; print gsub(/\./, ",", x); print x }`, "", "2\n1,2,3\n", "", ""},
 	{`{ print gsub(/\./, ","); print $0 }`, "1.2.3", "2\n1,2,3\n", "", ""},
@@ -843,9 +883,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", "", ""},