Repository: goplus/gop
Pull request #1153 (2022-04-22 08:39:06)

fmt uint128: print/scan


Patch:
diff --git a/builtin/ng/uint128.go b/builtin/ng/uint128.go
index 4fdfd8a0..e591a182 100644
--- a/builtin/ng/uint128.go
+++ b/builtin/ng/uint128.go
@@ -1,7 +1,10 @@
 package ng
 
 import (
+	"fmt"
+	"math/big"
 	"math/bits"
+	"strconv"
 )
 
 const (
@@ -10,10 +13,13 @@ const (
 
 const (
 	maxUint64 = (1 << 64) - 1
+	intSize   = 32 << (^uint(0) >> 63)
 )
 
 type Gop_ninteger = uint
 
+// -----------------------------------------------------------------------------
+
 type Uint128 struct {
 	hi, lo uint64
 }
@@ -72,6 +78,58 @@ func Uint128_Cast__7(v int64) (out Uint128, inRange bool) {
 	return Uint128{lo: uint64(v)}, true
 }
 
+// Uint128_Cast: func uint128(v *big.Int) uint128
+func Uint128_Cast__8(v *big.Int) Uint128 {
+	out, _ := Uint128_Cast__9(v)
+	return out
+}
+
+// Uint128_Cast: func uint128(v *big.Int) (uint128, bool)
+func Uint128_Cast__9(v *big.Int) (out Uint128, inRange bool) {
+	if v.Sign() < 0 {
+		return out, false
+	}
+
+	words := v.Bits()
+	switch intSize {
+	case 64:
+		lw := len(words)
+		switch lw {
+		case 0:
+			return Uint128{}, true
+		case 1:
+			return Uint128{lo: uint64(words[0])}, true
+		case 2:
+			return Uint128{hi: uint64(words[1]), lo: uint64(words[0])}, true
+		default:
+			return Uint128{hi: maxUint64, lo: maxUint64}, false
+		}
+
+	case 32:
+		lw := len(words)
+		switch lw {
+		case 0:
+			return Uint128{}, true
+		case 1:
+			return Uint128{lo: uint64(words[0])}, true
+		case 2:
+			return Uint128{lo: (uint64(words[1]) << 32) | (uint64(words[0]))}, true
+		case 3:
+			return Uint128{hi: uint64(words[2]), lo: (uint64(words[1]) << 32) | (uint64(words[0]))}, true
+		case 4:
+			return Uint128{
+				hi: (uint64(words[3]) << 32) | (uint64(words[2])),
+				lo: (uint64(words[1]) << 32) | (uint64(words[0])),
+			}, true
+		default:
+			return Uint128{hi: maxUint64, lo: maxUint64}, false
+		}
+
+	default:
+		panic("unsupported bit size")
+	}
+}
+
 // Gop_Rcast: func float64(v uint128) float64
 func (u Uint128) Gop_Rcast__0() float64 {
 	if u.hi == 0 {
@@ -90,10 +148,84 @@ func (u Uint128) Gop_Rcast__2() (out uint64, inRange bool) {
 	return u.lo, u.hi == 0
 }
 
+// -----------------------------------------------------------------------------
+
 func (u Uint128) IsZero() bool {
 	return u.lo == 0 && u.hi == 0
 }
 
+func (u *Uint128) Scan(state fmt.ScanState, verb rune) (err error) {
+	t, err := state.Token(true, nil)
+	if err != nil {
+		return
+	}
+	v, err := ParseUint128(string(t), 10)
+	if err == nil {
+		*u = v
+	}
+	return
+}
+
+func (u Uint128) Format(s fmt.State, c rune) {
+	// TODO: not so good
+	u.BigInt().Format(s, c)
+}
+
+func (u Uint128) String() string {
+	return u.Text(10)
+}
+
+func (u Uint128) Text(base int) string {
+	if u.hi == 0 {
+		return strconv.FormatUint(u.lo, base)
+	}
+	// TODO: not so good
+	return u.BigInt().Text(base)
+}
+
+func (u Uint128) BigInt() *big.Int {
+	var v big.Int
+	u.ToBigInt(&v)
+	return &v
+}
+
+func (u Uint128) ToBigInt(b *big.Int) {
+	switch intSize {
+	case 64:
+		bits := b.Bits()
+		ln := len(bits)
+		if len(bits) < 2 {
+			bits = append(bits, make([]big.Word, 2-ln)...)
+		}
+		bits = bits[:2]
+		bits[0] = big.Word(u.lo)
+		bits[1] = big.Word(u.hi)
+		b.SetBits(bits)
+
+	case 32:
+		bits := b.Bits()
+		ln := len(bits)
+		if len(bits) < 4 {
+			bits = append(bits, make([]big.Word, 4-ln)...)
+		}
+		bits = bits[:4]
+		bits[0] = big.Word(u.lo & 0xFFFFFFFF)
+		bits[1] = big.Word(u.lo >> 32)
+		bits[2] = big.Word(u.hi & 0xFFFFFFFF)
+		bits[3] = big.Word(u.hi >> 32)
+		b.SetBits(bits)
+
+	default:
+		if u.hi > 0 {
+			b.SetUint64(u.hi)
+			b.Lsh(b, 64)
+		}
+		var lo big.Int
+		lo.SetUint64(u.lo)
+		b.Add(b, &lo)
+	}
+}
+
 // Bit returns the value of the i'th bit of x. That is, it returns (x>>i)&1.
 // The bit index i must be 0 <= i < 128
 func (u Uint128) Bit(i int) uint {
@@ -729,3 +861,24 @@ func quo128bin(u, by Uint128, uLeading0, byLeading0 uint) (q Uint128) {
 
 	return q
 }
+
+// -----------------------------------------------------------------------------
+
+func ParseUint128(s string, base int) (out Uint128, err error) {
+	b, ok := new(big.Int).SetString(s, base)
+	if !ok {
+		err = fmt.Errorf("invalid string: %q", s)
+		return
+	}
+	out, inRange := Uint128_Cast__9(b)
+	if !inRange {
+		err = fmt.Errorf("string %q was not in valid uint128 range", s)
+	}
+	return
+}
+
+func FormatUint128(i Uint128, base int) string {
+	return i.Text(base)
+}
+
+// -----------------------------------------------------------------------------


Test patch:
diff --git a/cl/run_test.go b/cl/run_test.go
new file mode 100644
index 00000000..0c36b020
--- /dev/null
+++ b/cl/run_test.go
@@ -0,0 +1,138 @@
+package cl_test
+
+import (
+	"bytes"
+	"io"
+	"log"
+	"os"
+	"os/exec"
+	"strconv"
+	"sync/atomic"
+	"testing"
+
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/parsertest"
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/gox"
+)
+
+var (
+	tmpDir     string
+	tmpFileIdx int64
+)
+
+func init() {
+	home, err := os.Getwd()
+	check(err)
+
+	tmpDir = home + "/.gop/tmp/"
+	err = os.MkdirAll(tmpDir, 0755)
+	check(err)
+}
+
+func check(err error) {
+	if err != nil {
+		log.Panicln(err)
+	}
+}
+
+func checkWith(err error, stdout, stderr io.Writer) {
+	if err != nil {
+		fatalWith(err, stdout, stderr)
+	}
+}
+
+func fatalWith(err error, stdout, stderr io.Writer) {
+	if o, ok := getBytes(stdout, stderr); ok {
+		os.Stderr.Write(o.Bytes())
+	}
+	log.Panicln(err)
+}
+
+type iBytes interface {
+	Bytes() []byte
+}
+
+func getBytes(stdout, stderr io.Writer) (o iBytes, ok bool) {
+	if o, ok = stderr.(iBytes); ok {
+		return
+	}
+	o, ok = stdout.(iBytes)
+	return
+}
+
+func goRun(t *testing.T, code []byte) string {
+	idx := atomic.AddInt64(&tmpFileIdx, 1)
+	infile := tmpDir + strconv.FormatInt(idx, 10) + ".go"
+	err := os.WriteFile(infile, []byte(code), 0666)
+	check(err)
+
+	var stdout, stderr bytes.Buffer
+	cmd := exec.Command("go", "run", infile)
+	cmd.Dir = tmpDir
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	err = cmd.Run()
+	os.Remove(infile)
+	checkWith(err, &stdout, &stderr)
+	return stdout.String()
+}
+
+func genGo(t *testing.T, conf *cl.Config, gopcode string) []byte {
+	cl.SetDisableRecover(true)
+	defer cl.SetDisableRecover(false)
+
+	fs := parsertest.NewSingleFileFS("/foo", "bar.gop", gopcode)
+	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("ParseFSDir:", err)
+	}
+	pkg, err := cl.NewPackage("", pkgs["main"], conf)
+	if err != nil {
+		t.Fatal("NewPackage:", err)
+	}
+	var b bytes.Buffer
+	err = gox.WriteTo(&b, pkg, false)
+	if err != nil {
+		t.Fatal("gox.WriteTo failed:", err)
+	}
+	return b.Bytes()
+}
+
+func testRun(t *testing.T, gopcode, expected string) {
+	code := genGo(t, gblConf, gopcode)
+	result := goRun(t, code)
+	if result != expected {
+		t.Fatalf("=> Result:\n%s\n=> Expected:\n%s\n", result, expected)
+	}
+}
+
+func TestInt128_println(t *testing.T) {
+	testRun(t, `
+var x uint128 = 1
+println x
+`, `1
+`)
+}
+
+func TestInt128_printf(t *testing.T) {
+	testRun(t, `
+var x uint128 = 1
+printf "%4d\n", x
+`, `   1
+`)
+}
+
+func TestInt128_scanf(t *testing.T) {
+	testRun(t, `
+import "fmt"
+
+var name string
+var age uint128
+fmt.Sscanf("Kim is 22 years old", "%s is %d years old", &name, &age)
+println name, age
+`, `Kim 22
+`)
+}