Repository: benhoyt/goawk (go)

Issue:
Fix how we handle numstr type when setting $0 directly
For example, this script prints `1` under GoAWK, but under (g)awk it prints `0` (presumably the correct answer):

```
BEGIN { $0="0"; print !$0 }
```

There's a commented-out test for this in `interp/interp_test.go`:

```go
{`BEGIN { $0="0"; print !$0 }`, "", "0\n", "", ""},
```

Pull request:
Fix field numeric string handling
Numeric strings are one of the annoying / dark corners of AWK, but we should handle them as per the POSIX spec and awk/gawk/mawk. This fixes that and uncomments the relevant tests.

There is a slight speed decrease, but it's small enough, and the recent https://github.com/benhoyt/goawk/commit/b05d0d983d4e96ae518c6af3e846fbb1a15040aa change means that overall GoAWK is faster anyway.

Test      | goawk |  orig |   awk |  gawk |  mawk 
--------- | ----- | ----- | ----- | ----- | -----
tt.01     |  3.01 |  2.85 |  1.00 |  1.84 |  2.35
tt.02     |  1.87 |  1.94 |  1.00 |  1.83 |  2.23
tt.02a    |  1.87 |  1.83 |  1.00 |  1.83 |  2.10
tt.03     |  1.40 |  1.43 |  1.00 |  3.89 |  1.85
tt.03a    |  1.38 |  1.48 |  1.00 |  4.08 |  1.77
tt.04     |  0.88 |  0.88 |  1.00 |  1.26 |  2.88
tt.05     |  0.98 |  1.00 |  1.00 |  1.67 |  2.36
tt.06     |  1.42 |  1.51 |  1.00 |  2.50 |  1.94
tt.07     |  1.36 |  1.46 |  1.00 |  1.52 |  1.65
tt.08     |  2.72 |  2.83 |  1.00 |  1.24 |  3.20
tt.09     |  2.33 |  2.38 |  1.00 |  2.46 |  5.45
tt.10     |  0.36 |  0.36 |  1.00 |  1.43 |  4.24
tt.10a    |  0.38 |  0.39 |  1.00 |  1.56 |  4.63
tt.11     |  2.32 |  2.32 |  1.00 |  1.09 |  3.34
tt.12     |  1.42 |  1.44 |  1.00 |  1.75 |  1.84
tt.13     |  3.03 |  3.04 |  1.00 |  3.27 |  6.22
tt.13a    |  1.97 |  1.96 |  1.00 |  1.85 |  4.49
tt.14     |  1.13 |  1.17 |  1.00 |  0.64 |  1.69
tt.15     |  0.69 |  0.71 |  1.00 |  1.05 |  2.32
tt.16     |  1.33 |  1.35 |  1.00 |  1.31 |  2.15
tt.big    |  1.62 |  1.63 |  1.00 |  1.88 |  3.90
tt.x1     |  1.56 |  1.64 |  1.00 |  1.35 |  3.43
tt.x2     |  1.00 |  1.05 |  1.00 |  1.16 |  2.67
--------- | ----- | ----- | ----- | ----- | -----
**Geo mean**  | **1.45** | **1.49** | **1.00** | **1.91** | **2.35**

Fixes #49.

Patch:
diff --git a/interp/functions.go b/interp/functions.go
index 72cff16..b5a5466 100644
--- a/interp/functions.go
+++ b/interp/functions.go
@@ -79,7 +79,7 @@ func (p *interp) callBuiltin(op Token, argExprs []Expr) (value, error) {
 				return null(), err
 			}
 		} else {
-			p.setLine(out)
+			p.setLine(out, true)
 		}
 		return num(float64(n)), nil
 	}
diff --git a/interp/interp.go b/interp/interp.go
index 42e1946..27d0bcb 100644
--- a/interp/interp.go
+++ b/interp/interp.go
@@ -88,13 +88,15 @@ type interp struct {
 	nativeFuncs []nativeFunc
 
 	// File, line, and field handling
-	filename    value
-	line        string
-	lineNum     int
-	fileLineNum int
-	fields      []string
-	numFields   int
-	haveFields  bool
+	filename        value
+	line            string
+	lineIsTrueStr   bool
+	lineNum         int
+	fileLineNum     int
+	fields          []string
+	fieldsIsTrueStr []bool
+	numFields       int
+	haveFields      bool
 
 	// Built-in variables
 	argc            int
@@ -369,7 +371,7 @@ lineLoop:
 		if err != nil {
 			return err
 		}
-		p.setLine(line)
+		p.setLine(line, false)
 
 		// Execute all the pattern-action blocks for each line
 		for i, action := range actions {
@@ -906,7 +908,7 @@ func (p *interp) eval(expr Expr) (value, error) {
 				return null(), err
 			}
 		} else {
-			p.setLine(line)
+			p.setLine(line, false)
 		}
 		return num(1), nil
 
@@ -1033,11 +1035,14 @@ func (p *interp) setVar(scope VarScope, index int, v value) error {
 			p.numFields = numFields
 			if p.numFields < len(p.fields) {
 				p.fields = p.fields[:p.numFields]
+				p.fieldsIsTrueStr = p.fieldsIsTrueStr[:p.numFields]
 			}
 			for i := len(p.fields); i < p.numFields; i++ {
 				p.fields = append(p.fields, "")
+				p.fieldsIsTrueStr = append(p.fieldsIsTrueStr, false)
 			}
 			p.line = strings.Join(p.fields, p.outputFieldSep)
+			p.lineIsTrueStr = true
 		case V_NR:
 			p.lineNum = int(v.num())
 		case V_RLENGTH:
@@ -1119,19 +1124,27 @@ func (p *interp) getField(index int) (value, error) {
 		return null(), newError("field index negative: %d", index)
 	}
 	if index == 0 {
-		return numStr(p.line), nil
+		if p.lineIsTrueStr {
+			return str(p.line), nil
+		} else {
+			return numStr(p.line), nil
+		}
 	}
 	p.ensureFields()
 	if index > len(p.fields) {
 		return str(""), nil
 	}
-	return numStr(p.fields[index-1]), nil
+	if p.fieldsIsTrueStr[index-1] {
+		return str(p.fields[index-1]), nil
+	} else {
+		return numStr(p.fields[index-1]), nil
+	}
 }
 
 // Sets a single field, equivalent to "$index = value"
 func (p *interp) setField(index int, value string) error {
 	if index == 0 {
-		p.setLine(value)
+		p.setLine(value, true)
 		return nil
 	}
 	if index < 0 {
@@ -1144,10 +1157,13 @@ func (p *interp) setField(index int, value string) error {
 	p.ensureFields()
 	for i := len(p.fields); i < index; i++ {
 		p.fields = append(p.fields, "")
+		p.fieldsIsTrueStr = append(p.fieldsIsTrueStr, true)
 	}
 	p.fields[index-1] = value
+	p.fieldsIsTrueStr[index-1] = true
 	p.numFields = len(p.fields)
 	p.line = strings.Join(p.fields, p.outputFieldSep)
+	p.lineIsTrueStr = true
 	return nil
 }
 
diff --git a/interp/io.go b/interp/io.go
index c9a651f..f38d7c3 100644
--- a/interp/io.go
+++ b/interp/io.go
@@ -288,8 +288,9 @@ func (p *interp) setFile(filename string) {
 
 // Setup for a new input line (but don't parse it into fields till we
 // need to)
-func (p *interp) setLine(line string) {
+func (p *interp) setLine(line string, isTrueStr bool) {
 	p.line = line
+	p.lineIsTrueStr = isTrueStr
 	p.haveFields = false
 }
 
@@ -329,6 +330,7 @@ func (p *interp) ensureFields() {
 		p.fields = fields
 	}
 
+	p.fieldsIsTrueStr = make([]bool, len(p.fields))
 	p.numFields = len(p.fields)
 }
 


Test patch:
diff --git a/goawk_test.go b/goawk_test.go
index 0cef26a..a6d1705 100644
--- a/goawk_test.go
+++ b/goawk_test.go
@@ -231,9 +231,6 @@ func TestGAWK(t *testing.T) {
 		"sigpipe1":     true, // probable race condition: sometimes fails, sometimes passes
 
 		"parse1": true, // incorrect parsing of $$a++++ (see TODOs in interp_test.go too)
-
-		"nfldstr": true, // invalid handling of '!$0' when $0="0"
-		"zeroe0":  true, // difference in handling of numStr typing when setting $0 and $1
 	}
 
 	dontRunOnWindows := map[string]bool{
diff --git a/interp/interp_test.go b/interp/interp_test.go
index bdd8914..1d24333 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -149,7 +149,7 @@ BEGIN {
 	{`BEGIN { print !42, !1, !0, !!42, !!1, !!0 }`, "", "0 0 1 1 1 0\n", "", ""},
 	{`BEGIN { print !42, !1, !0, !!42, !!1, !!0 }`, "", "0 0 1 1 1 0\n", "", ""},
 	{`BEGIN { print +4, +"3", +0, +-3, -3, - -4, -"3" }`, "", "4 3 0 -3 -3 4 -3\n", "", ""},
-	// TODO: {`BEGIN { $0="0"; print !$0 }`, "", "0\n", "", ""},
+	{`BEGIN { $0="0"; print !$0 }`, "", "0\n", "", ""},
 	{`BEGIN { $0="1"; print !$0 }`, "", "0\n", "", ""},
 	{`{ print !$0 }`, "0\n", "1\n", "", ""},
 	{`{ print !$0 }`, "1\n", "0\n", "", ""},
@@ -171,6 +171,12 @@ BEGIN {
 	{`{ print ($1>2) }`, "1\n1.0\n+1", "0\n0\n0\n", "", ""},
 	{`BEGIN { print (0>=1, 1>=1, 2>=1, "12">="2") }`, "", "0 1 1 0\n", "", ""},
 	{`{ print ($1>=2) }`, "1\n1.0\n+1", "0\n0\n0\n", "", ""},
+	{`{ print($0<2) }`, "10", "0\n", "", ""},
+	{`{ print($1<2) }`, "10", "0\n", "", ""},
+	{`{ print($1<2) }`, "10x", "1\n", "", ""},
+	{`BEGIN { $0="10"; print($0<2) }`, "", "1\n", "", ""},
+	{`BEGIN { $1="10"; print($1<2) }`, "", "1\n", "", ""},
+	{`BEGIN { $1="10x"; print($1<2) }`, "", "1\n", "", ""},
 
 	// Short-circuit && and || operators
 	{`
@@ -233,7 +239,7 @@ BEGIN {
 	// Conditional ?: expression
 	{`{ print /x/?"t":"f" }`, "x\ny\nxx\nz\n", "t\nf\nt\nf\n", "", ""},
 	{`BEGIN { print 1?2?3:4:5, 1?0?3:4:5, 0?2?3:4:5 }`, "", "3 4 5\n", "", ""},
-	// TODO: {`BEGIN { $0="0"; print ($0?1:0) }`, "", "1\n", "", ""},
+	{`BEGIN { $0="0"; print ($0?1:0) }`, "", "1\n", "", ""},
 	{`{ print $0?1:0 }`, "0\n", "0\n", "", ""},
 	{`{ print $0?1:0 }`, "1\n", "1\n", "", ""},
 	{`BEGIN { $0="1"; print ($0?1:0) }`, "", "1\n", "", ""},