Repository: benhoyt/goawk (go)

Issue:
Exit status code in BEGIN should apply if END has exit
If there's an `exit` in the `BEGIN` block with an exit code, that status code should be returned, even if there's an `exit` in the `END` block with a zero exit code. For example:

```
$ goawk 'BEGIN { exit 23 }; END { exit }'; echo $?
0
```

All other AWKs print 23.

Thanks @raygard for the bug report.

Pull request:
Ensure "exit" without status code doesn't set exit code
Also update stringer version, as I was getting this error with the current version for some reason:
https://github.com/golang/go/issues/52710

Fixes #191.

Patch:
diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go
index ecfcba6..0adf18f 100644
--- a/internal/compiler/compiler.go
+++ b/internal/compiler/compiler.go
@@ -462,10 +462,10 @@ func (c *compiler) stmt(stmt ast.Stmt) {
 	case *ast.ExitStmt:
 		if s.Status != nil {
 			c.expr(s.Status)
+			c.add(ExitStatus)
 		} else {
-			c.expr(&ast.NumExpr{0})
+			c.add(Exit)
 		}
-		c.add(Exit)
 
 	case *ast.DeleteStmt:
 		scope, index := c.arrayInfo(s.Array)
diff --git a/internal/compiler/opcode_string.go b/internal/compiler/opcode_string.go
index 2fa8ec4..6929e48 100644
--- a/internal/compiler/opcode_string.go
+++ b/internal/compiler/opcode_string.go
@@ -79,32 +79,33 @@ func _() {
 	_ = x[Next-68]
 	_ = x[Nextfile-69]
 	_ = x[Exit-70]
-	_ = x[ForIn-71]
-	_ = x[BreakForIn-72]
-	_ = x[CallBuiltin-73]
-	_ = x[CallLengthArray-74]
-	_ = x[CallSplit-75]
-	_ = x[CallSplitSep-76]
-	_ = x[CallSprintf-77]
-	_ = x[CallUser-78]
-	_ = x[CallNative-79]
-	_ = x[Return-80]
-	_ = x[ReturnNull-81]
-	_ = x[Nulls-82]
-	_ = x[Print-83]
-	_ = x[Printf-84]
-	_ = x[Getline-85]
-	_ = x[GetlineField-86]
-	_ = x[GetlineGlobal-87]
-	_ = x[GetlineLocal-88]
-	_ = x[GetlineSpecial-89]
-	_ = x[GetlineArray-90]
-	_ = x[EndOpcode-91]
+	_ = 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]
 }
 
-const _Opcode_name = "NopNumStrDupeDropSwapFieldFieldIntFieldByNameFieldByNameStrGlobalLocalSpecialArrayGlobalArrayLocalInGlobalInLocalAssignFieldAssignGlobalAssignLocalAssignSpecialAssignArrayGlobalAssignArrayLocalDeleteDeleteAllIncrFieldIncrGlobalIncrLocalIncrSpecialIncrArrayGlobalIncrArrayLocalAugAssignFieldAugAssignGlobalAugAssignLocalAugAssignSpecialAugAssignArrayGlobalAugAssignArrayLocalRegexIndexMultiConcatMultiAddSubtractMultiplyDividePowerModuloEqualsNotEqualsLessGreaterLessOrEqualGreaterOrEqualConcatMatchNotMatchNotUnaryMinusUnaryPlusBooleanJumpJumpFalseJumpTrueJumpEqualsJumpNotEqualsJumpLessJumpGreaterJumpLessOrEqualJumpGreaterOrEqualNextNextfileExitForInBreakForInCallBuiltinCallLengthArrayCallSplitCallSplitSepCallSprintfCallUserCallNativeReturnReturnNullNullsPrintPrintfGetlineGetlineFieldGetlineGlobalGetlineLocalGetlineSpecialGetlineArrayEndOpcode"
+const _Opcode_name = "NopNumStrDupeDropSwapFieldFieldIntFieldByNameFieldByNameStrGlobalLocalSpecialArrayGlobalArrayLocalInGlobalInLocalAssignFieldAssignGlobalAssignLocalAssignSpecialAssignArrayGlobalAssignArrayLocalDeleteDeleteAllIncrFieldIncrGlobalIncrLocalIncrSpecialIncrArrayGlobalIncrArrayLocalAugAssignFieldAugAssignGlobalAugAssignLocalAugAssignSpecialAugAssignArrayGlobalAugAssignArrayLocalRegexIndexMultiConcatMultiAddSubtractMultiplyDividePowerModuloEqualsNotEqualsLessGreaterLessOrEqualGreaterOrEqualConcatMatchNotMatchNotUnaryMinusUnaryPlusBooleanJumpJumpFalseJumpTrueJumpEqualsJumpNotEqualsJumpLessJumpGreaterJumpLessOrEqualJumpGreaterOrEqualNextNextfileExitExitStatusForInBreakForInCallBuiltinCallLengthArrayCallSplitCallSplitSepCallSprintfCallUserCallNativeReturnReturnNullNullsPrintPrintfGetlineGetlineFieldGetlineGlobalGetlineLocalGetlineSpecialGetlineArrayEndOpcode"
 
-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, 652, 662, 673, 688, 697, 709, 720, 728, 738, 744, 754, 759, 764, 770, 777, 789, 802, 814, 828, 840, 849}
+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}
 
 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 41f1817..4dca3dd 100644
--- a/internal/compiler/opcodes.go
+++ b/internal/compiler/opcodes.go
@@ -1,6 +1,6 @@
 package compiler
 
-//go:generate go run golang.org/x/tools/cmd/stringer@v0.1.8 -type=Opcode,AugOp,BuiltinOp
+//go:generate go run golang.org/x/tools/cmd/stringer@v0.10.0 -type=Opcode,AugOp,BuiltinOp
 
 // Opcode represents a single virtual machine instruction (or argument). The
 // comments beside each opcode show any arguments that instruction consumes.
@@ -109,6 +109,7 @@ const (
 	Next
 	Nextfile
 	Exit
+	ExitStatus
 	ForIn // varScope varIndex arrayScope arrayIndex offset
 	BreakForIn
 
diff --git a/interp/vm.go b/interp/vm.go
index 2ab593c..4edeef4 100644
--- a/interp/vm.go
+++ b/interp/vm.go
@@ -591,10 +591,13 @@ func (p *interp) execute(code []compiler.Opcode) error {
 			return errNextfile
 
 		case compiler.Exit:
-			p.exitStatus = int(p.pop().num())
 			// Return special errExit value "caught" by top-level executor
 			return errExit
 
+		case compiler.ExitStatus:
+			p.exitStatus = int(p.pop().num())
+			return errExit
+
 		case compiler.ForIn:
 			varScope := code[ip]
 			varIndex := code[ip+1]


Test patch:
diff --git a/interp/interp_test.go b/interp/interp_test.go
index e32af7b..00cc30d 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -1607,6 +1607,9 @@ func TestExit(t *testing.T) {
 		{`{ print "x"; exit 1+2; print "y" }  END { print "z" }`, "x\nz\n", 3},
 		{`END { print "x"; exit; print "y" }`, "x\n", 0},
 		{`END { print "x"; exit 1+2; print "y" }`, "x\n", 3},
+		{`BEGIN { print "x"; exit 42 }  END { print "z"; exit }`, "x\nz\n", 42},
+		{`BEGIN { print "x"; exit }  END { print "z"; exit 43 }`, "x\nz\n", 43},
+		{`BEGIN { print "x"; exit 44 }  { print "a"; exit }`, "x\n", 44},
 	}
 	for _, test := range tests {
 		t.Run(test.src, func(t *testing.T) {