From 527610763b882b56c52d77acf1b75c2f4233c973 Mon Sep 17 00:00:00 2001
From: apocelipes <seve3r@outlook.com>
Date: Thu, 15 Aug 2024 18:13:58 +0000
Subject: [PATCH] math/big,regexp: implement the encoding.TextAppender
 interface

For #62384

Change-Id: I1557704c6a0f9c6f3b9aad001374dd5cdbc99065
GitHub-Last-Rev: c258d18ccedab5feeb481a2431d5647bde7e5c58
GitHub-Pull-Request: golang/go#68893
Reviewed-on: https://go-review.googlesource.com/c/go/+/605758
Reviewed-by: Ian Lance Taylor <iant@google.com>
Commit-Queue: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Auto-Submit: Ian Lance Taylor <iant@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Griesemer <gri@google.com>
---
 api/next/62384.txt                           |  4 ++
 doc/next/6-stdlib/99-minor/math/big/62384.md |  1 +
 doc/next/6-stdlib/99-minor/regexp/62384.md   |  1 +
 src/math/big/floatmarsh.go                   | 16 +++++---
 src/math/big/floatmarsh_test.go              | 43 ++++++++++++++++++++
 src/math/big/intmarsh.go                     | 10 +++--
 src/math/big/intmarsh_test.go                | 33 +++++++++++++++
 src/math/big/ratconv.go                      |  9 ++--
 src/math/big/ratmarsh.go                     | 13 ++++--
 src/math/big/ratmarsh_test.go                | 23 +++++++++++
 src/regexp/all_test.go                       | 15 +++++++
 src/regexp/regexp.go                         | 12 +++++-
 12 files changed, 161 insertions(+), 19 deletions(-)
 create mode 100644 doc/next/6-stdlib/99-minor/math/big/62384.md
 create mode 100644 doc/next/6-stdlib/99-minor/regexp/62384.md

diff --git a/api/next/62384.txt b/api/next/62384.txt
index ece5d9fd801..af7fc5363cc 100644
--- a/api/next/62384.txt
+++ b/api/next/62384.txt
@@ -5,3 +5,7 @@ pkg encoding, type TextAppender interface, AppendText([]uint8) ([]uint8, error)
 pkg net/url, method (*URL) AppendBinary([]uint8) ([]uint8, error) #62384
 pkg log/slog, method (Level) AppendText([]uint8) ([]uint8, error) #62384
 pkg log/slog, method (*LevelVar) AppendText([]uint8) ([]uint8, error) #62384
+pkg math/big, method (*Float) AppendText([]uint8) ([]uint8, error) #62384
+pkg math/big, method (*Int) AppendText([]uint8) ([]uint8, error) #62384
+pkg math/big, method (*Rat) AppendText([]uint8) ([]uint8, error) #62384
+pkg regexp, method (*Regexp) AppendText([]uint8) ([]uint8, error) #62384
diff --git a/doc/next/6-stdlib/99-minor/math/big/62384.md b/doc/next/6-stdlib/99-minor/math/big/62384.md
new file mode 100644
index 00000000000..4a9418818d0
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/math/big/62384.md
@@ -0,0 +1 @@
+[Float], [Int] and [Rat] now implement the [encoding.TextAppender] interface.
diff --git a/doc/next/6-stdlib/99-minor/regexp/62384.md b/doc/next/6-stdlib/99-minor/regexp/62384.md
new file mode 100644
index 00000000000..9dcdbad1e88
--- /dev/null
+++ b/doc/next/6-stdlib/99-minor/regexp/62384.md
@@ -0,0 +1 @@
+[Regexp] now implements the [encoding.TextAppender] interface.
diff --git a/src/math/big/floatmarsh.go b/src/math/big/floatmarsh.go
index 16be9469714..39c22077db6 100644
--- a/src/math/big/floatmarsh.go
+++ b/src/math/big/floatmarsh.go
@@ -106,15 +106,21 @@ func (z *Float) GobDecode(buf []byte) error {
 	return nil
 }
 
-// MarshalText implements the [encoding.TextMarshaler] interface.
+// AppendText implements the [encoding.TextAppender] interface.
 // Only the [Float] value is marshaled (in full precision), other
 // attributes such as precision or accuracy are ignored.
-func (x *Float) MarshalText() (text []byte, err error) {
+func (x *Float) AppendText(b []byte) ([]byte, error) {
 	if x == nil {
-		return []byte("<nil>"), nil
+		return append(b, "<nil>"...), nil
 	}
-	var buf []byte
-	return x.Append(buf, 'g', -1), nil
+	return x.Append(b, 'g', -1), nil
+}
+
+// MarshalText implements the [encoding.TextMarshaler] interface.
+// Only the [Float] value is marshaled (in full precision), other
+// attributes such as precision or accuracy are ignored.
+func (x *Float) MarshalText() (text []byte, err error) {
+	return x.AppendText(nil)
 }
 
 // UnmarshalText implements the [encoding.TextUnmarshaler] interface.
diff --git a/src/math/big/floatmarsh_test.go b/src/math/big/floatmarsh_test.go
index 20def68a6d3..339cb537642 100644
--- a/src/math/big/floatmarsh_test.go
+++ b/src/math/big/floatmarsh_test.go
@@ -171,3 +171,46 @@ func TestFloatGobDecodeInvalid(t *testing.T) {
 		}
 	}
 }
+
+func TestFloatAppendText(t *testing.T) {
+	for _, test := range floatVals {
+		for _, sign := range []string{"", "+", "-"} {
+			for _, prec := range []uint{0, 1, 2, 10, 53, 64, 100, 1000} {
+				if prec > 53 && testing.Short() {
+					continue
+				}
+				x := sign + test
+				var tx Float
+				_, _, err := tx.SetPrec(prec).Parse(x, 0)
+				if err != nil {
+					t.Errorf("parsing of %s (prec = %d) failed (invalid test case): %v", x, prec, err)
+					continue
+				}
+				buf := make([]byte, 4, 32)
+				b, err := tx.AppendText(buf)
+				if err != nil {
+					t.Errorf("marshaling of %v (prec = %d) failed: %v", &tx, prec, err)
+					continue
+				}
+				var rx Float
+				rx.SetPrec(prec)
+				if err := rx.UnmarshalText(b[4:]); err != nil {
+					t.Errorf("unmarshaling of %v (prec = %d) failed: %v", &tx, prec, err)
+					continue
+				}
+				if rx.Cmp(&tx) != 0 {
+					t.Errorf("AppendText of %v (prec = %d) failed: got %v want %v", &tx, prec, &rx, &tx)
+				}
+			}
+		}
+	}
+}
+
+func TestFloatAppendTextNil(t *testing.T) {
+	var x *Float
+	buf := make([]byte, 4, 16)
+	data, _ := x.AppendText(buf)
+	if string(data[4:]) != "<nil>" {
+		t.Errorf("got %q, want <nil>", data[4:])
+	}
+}
diff --git a/src/math/big/intmarsh.go b/src/math/big/intmarsh.go
index 56eeefb884d..858ca0faba5 100644
--- a/src/math/big/intmarsh.go
+++ b/src/math/big/intmarsh.go
@@ -45,12 +45,14 @@ func (z *Int) GobDecode(buf []byte) error {
 	return nil
 }
 
+// AppendText implements the [encoding.TextAppender] interface.
+func (x *Int) AppendText(b []byte) (text []byte, err error) {
+	return x.Append(b, 10), nil
+}
+
 // MarshalText implements the [encoding.TextMarshaler] interface.
 func (x *Int) MarshalText() (text []byte, err error) {
-	if x == nil {
-		return []byte("<nil>"), nil
-	}
-	return x.abs.itoa(x.neg, 10), nil
+	return x.AppendText(nil)
 }
 
 // UnmarshalText implements the [encoding.TextUnmarshaler] interface.
diff --git a/src/math/big/intmarsh_test.go b/src/math/big/intmarsh_test.go
index 8e7d29f9ddc..681f3dd9468 100644
--- a/src/math/big/intmarsh_test.go
+++ b/src/math/big/intmarsh_test.go
@@ -132,3 +132,36 @@ func TestIntXMLEncoding(t *testing.T) {
 		}
 	}
 }
+
+func TestIntAppendText(t *testing.T) {
+	for _, test := range encodingTests {
+		for _, sign := range []string{"", "+", "-"} {
+			x := sign + test
+			var tx Int
+			tx.SetString(x, 10)
+			buf := make([]byte, 4, 32)
+			b, err := tx.AppendText(buf)
+			if err != nil {
+				t.Errorf("marshaling of %s failed: %s", &tx, err)
+				continue
+			}
+			var rx Int
+			if err := rx.UnmarshalText(b[4:]); err != nil {
+				t.Errorf("unmarshaling of %s failed: %s", &tx, err)
+				continue
+			}
+			if rx.Cmp(&tx) != 0 {
+				t.Errorf("AppendText of %s failed: got %s want %s", &tx, &rx, &tx)
+			}
+		}
+	}
+}
+
+func TestIntAppendTextNil(t *testing.T) {
+	var x *Int
+	buf := make([]byte, 4, 16)
+	data, _ := x.AppendText(buf)
+	if string(data[4:]) != "<nil>" {
+		t.Errorf("got %q, want <nil>", data[4:])
+	}
+}
diff --git a/src/math/big/ratconv.go b/src/math/big/ratconv.go
index dd99aecdc04..12f9888c370 100644
--- a/src/math/big/ratconv.go
+++ b/src/math/big/ratconv.go
@@ -299,12 +299,13 @@ func scanExponent(r io.ByteScanner, base2ok, sepOk bool) (exp int64, base int, e
 
 // String returns a string representation of x in the form "a/b" (even if b == 1).
 func (x *Rat) String() string {
-	return string(x.marshal())
+	return string(x.marshal(nil))
 }
 
-// marshal implements String returning a slice of bytes
-func (x *Rat) marshal() []byte {
-	var buf []byte
+// marshal implements [Rat.String] returning a slice of bytes.
+// It appends the string representation of x in the form "a/b" (even if b == 1) to buf,
+// and returns the extended buffer.
+func (x *Rat) marshal(buf []byte) []byte {
 	buf = x.a.Append(buf, 10)
 	buf = append(buf, '/')
 	if len(x.b.abs) != 0 {
diff --git a/src/math/big/ratmarsh.go b/src/math/big/ratmarsh.go
index 69628294531..0457fc9517d 100644
--- a/src/math/big/ratmarsh.go
+++ b/src/math/big/ratmarsh.go
@@ -68,12 +68,17 @@ func (z *Rat) GobDecode(buf []byte) error {
 	return nil
 }
 
-// MarshalText implements the [encoding.TextMarshaler] interface.
-func (x *Rat) MarshalText() (text []byte, err error) {
+// AppendText implements the [encoding.TextAppender] interface.
+func (x *Rat) AppendText(b []byte) ([]byte, error) {
 	if x.IsInt() {
-		return x.a.MarshalText()
+		return x.a.AppendText(b)
 	}
-	return x.marshal(), nil
+	return x.marshal(b), nil
+}
+
+// MarshalText implements the [encoding.TextMarshaler] interface.
+func (x *Rat) MarshalText() (text []byte, err error) {
+	return x.AppendText(nil)
 }
 
 // UnmarshalText implements the [encoding.TextUnmarshaler] interface.
diff --git a/src/math/big/ratmarsh_test.go b/src/math/big/ratmarsh_test.go
index 15c933efa6d..7d139bcad8e 100644
--- a/src/math/big/ratmarsh_test.go
+++ b/src/math/big/ratmarsh_test.go
@@ -136,3 +136,26 @@ func TestRatGobDecodeShortBuffer(t *testing.T) {
 		}
 	}
 }
+
+func TestRatAppendText(t *testing.T) {
+	for _, num := range ratNums {
+		for _, denom := range ratDenoms {
+			var tx Rat
+			tx.SetString(num + "/" + denom)
+			buf := make([]byte, 4, 32)
+			b, err := tx.AppendText(buf)
+			if err != nil {
+				t.Errorf("marshaling of %s failed: %s", &tx, err)
+				continue
+			}
+			var rx Rat
+			if err := rx.UnmarshalText(b[4:]); err != nil {
+				t.Errorf("unmarshaling of %s failed: %s", &tx, err)
+				continue
+			}
+			if rx.Cmp(&tx) != 0 {
+				t.Errorf("AppendText of %s failed: got %s want %s", &tx, &rx, &tx)
+			}
+		}
+	}
+}
diff --git a/src/regexp/all_test.go b/src/regexp/all_test.go
index c9c046b61d0..ead184d2868 100644
--- a/src/regexp/all_test.go
+++ b/src/regexp/all_test.go
@@ -965,6 +965,21 @@ func TestUnmarshalText(t *testing.T) {
 		if unmarshaled.String() != goodRe[i] {
 			t.Errorf("UnmarshalText returned unexpected value: %s", unmarshaled.String())
 		}
+
+		buf := make([]byte, 4, 32)
+		marshalAppend, err := re.AppendText(buf)
+		if err != nil {
+			t.Errorf("regexp %#q failed to marshal: %s", re, err)
+			continue
+		}
+		marshalAppend = marshalAppend[4:]
+		if err := unmarshaled.UnmarshalText(marshalAppend); err != nil {
+			t.Errorf("regexp %#q failed to unmarshal: %s", re, err)
+			continue
+		}
+		if unmarshaled.String() != goodRe[i] {
+			t.Errorf("UnmarshalText returned unexpected value: %s", unmarshaled.String())
+		}
 	}
 	t.Run("invalid pattern", func(t *testing.T) {
 		re := new(Regexp)
diff --git a/src/regexp/regexp.go b/src/regexp/regexp.go
index e06099425eb..253415fb6a4 100644
--- a/src/regexp/regexp.go
+++ b/src/regexp/regexp.go
@@ -1277,14 +1277,22 @@ func (re *Regexp) Split(s string, n int) []string {
 	return strings
 }
 
-// MarshalText implements [encoding.TextMarshaler]. The output
+// AppendText implements [encoding.TextAppender]. The output
 // matches that of calling the [Regexp.String] method.
 //
 // Note that the output is lossy in some cases: This method does not indicate
 // POSIX regular expressions (i.e. those compiled by calling [CompilePOSIX]), or
 // those for which the [Regexp.Longest] method has been called.
+func (re *Regexp) AppendText(b []byte) ([]byte, error) {
+	return append(b, re.String()...), nil
+}
+
+// MarshalText implements [encoding.TextMarshaler]. The output
+// matches that of calling the [Regexp.AppendText] method.
+//
+// See [Regexp.AppendText] for more information.
 func (re *Regexp) MarshalText() ([]byte, error) {
-	return []byte(re.String()), nil
+	return re.AppendText(nil)
 }
 
 // UnmarshalText implements [encoding.TextUnmarshaler] by calling
-- 
GitLab