mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
Cleanup, docs and tests.
This commit is contained in:
parent
da67ba8a4a
commit
5c643181a4
|
@ -142,6 +142,8 @@ func makeMap(input Inputs) (vals.Map, error) {
|
|||
// Output `$low`, `$low` + `$step`, ..., proceeding as long as smaller than
|
||||
// `$high`. If not given, `$low` defaults to 0.
|
||||
//
|
||||
// This command is [exactness-preserving](#exactness-preserving).
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ```elvish-transcript
|
||||
|
@ -156,19 +158,24 @@ func makeMap(input Inputs) (vals.Map, error) {
|
|||
// ▶ 5
|
||||
// ```
|
||||
//
|
||||
// Beware floating point oddities:
|
||||
// When using floating-point numbers, beware that computation errors can result
|
||||
// in an unexpected number of outputs:
|
||||
//
|
||||
// ```elvish-transcript
|
||||
// ~> range 0 0.8 &step=.1
|
||||
// ▶ 0
|
||||
// ▶ 0.1
|
||||
// ▶ 0.2
|
||||
// ▶ 0.30000000000000004
|
||||
// ▶ 0.4
|
||||
// ▶ 0.5
|
||||
// ▶ 0.6
|
||||
// ▶ 0.7
|
||||
// ▶ 0.7999999999999999
|
||||
// ~> range 0.9 &step=0.3
|
||||
// ▶ (num 0.0)
|
||||
// ▶ (num 0.3)
|
||||
// ▶ (num 0.6)
|
||||
// ▶ (num 0.8999999999999999)
|
||||
// ```
|
||||
//
|
||||
// Using exact rationals can avoid this problem:
|
||||
//
|
||||
// ```elvish-transcript
|
||||
// ~> range 9/10 &step=3/10
|
||||
// ▶ (num 0)
|
||||
// ▶ (num 3/10)
|
||||
// ▶ (num 3/5)
|
||||
// ```
|
||||
//
|
||||
// Etymology:
|
||||
|
|
|
@ -2,6 +2,7 @@ package eval_test
|
|||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
. "src.elv.sh/pkg/eval"
|
||||
|
@ -48,6 +49,22 @@ func TestRange(t *testing.T) {
|
|||
That(`range 3`).Puts(0, 1, 2),
|
||||
That(`range 1 3`).Puts(1, 2),
|
||||
That(`range 0 10 &step=3`).Puts(0, 3, 6, 9),
|
||||
|
||||
That(`range 10_000_000_000_000_000_000 10_000_000_000_000_000_003`).
|
||||
Puts(
|
||||
vals.ParseNum("10_000_000_000_000_000_000"),
|
||||
vals.ParseNum("10_000_000_000_000_000_001"),
|
||||
vals.ParseNum("10_000_000_000_000_000_002")),
|
||||
That(`range 10_000_000_000_000_000_000 10_000_000_000_000_000_003 &step=2`).
|
||||
Puts(
|
||||
vals.ParseNum("10_000_000_000_000_000_000"),
|
||||
vals.ParseNum("10_000_000_000_000_000_002")),
|
||||
|
||||
That(`range 23/10`).Puts(0, 1, 2),
|
||||
That(`range 1/10 23/10`).Puts(
|
||||
big.NewRat(1, 10), big.NewRat(11, 10), big.NewRat(21, 10)),
|
||||
That(`range 1/10 9/10 &step=3/10`).Puts(
|
||||
big.NewRat(1, 10), big.NewRat(4, 10), big.NewRat(7, 10)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -129,9 +129,9 @@ func TestPrintf(t *testing.T) {
|
|||
That(`printf '%t' $true`).Prints("true"),
|
||||
That(`printf '%t' $nil`).Prints("false"),
|
||||
|
||||
That(`printf '%3d' (float64 5)`).Prints(" 5"),
|
||||
That(`printf '%3d' (num 5)`).Prints(" 5"),
|
||||
That(`printf '%3d' 5`).Prints(" 5"),
|
||||
That(`printf '%08b' (float64 5)`).Prints("00000101"),
|
||||
That(`printf '%08b' (num 5)`).Prints("00000101"),
|
||||
That(`printf '%08b' 5`).Prints("00000101"),
|
||||
|
||||
That(`printf '%.1f' 3.1415`).Prints("3.1"),
|
||||
|
|
|
@ -148,110 +148,71 @@ func toFloat64(f float64) float64 {
|
|||
// ```
|
||||
|
||||
func lt(nums ...vals.Num) bool {
|
||||
return chainCompare(nums, func(pair vals.NumSlice) bool {
|
||||
switch pair := pair.(type) {
|
||||
case []int:
|
||||
return pair[0] < pair[1]
|
||||
case []*big.Int:
|
||||
return pair[0].Cmp(pair[1]) < 0
|
||||
case []*big.Rat:
|
||||
return pair[0].Cmp(pair[1]) < 0
|
||||
case []float64:
|
||||
return pair[0] < pair[1]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
})
|
||||
return chainCompare(nums,
|
||||
func(a, b int) bool { return a < b },
|
||||
func(a, b *big.Int) bool { return a.Cmp(b) < 0 },
|
||||
func(a, b *big.Rat) bool { return a.Cmp(b) < 0 },
|
||||
func(a, b float64) bool { return a < b })
|
||||
|
||||
}
|
||||
|
||||
func le(nums ...vals.Num) bool {
|
||||
return chainCompare(nums, func(pair vals.NumSlice) bool {
|
||||
switch pair := pair.(type) {
|
||||
case []int:
|
||||
return pair[0] <= pair[1]
|
||||
case []*big.Int:
|
||||
return pair[0].Cmp(pair[1]) <= 0
|
||||
case []*big.Rat:
|
||||
return pair[0].Cmp(pair[1]) <= 0
|
||||
case []float64:
|
||||
return pair[0] <= pair[1]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
})
|
||||
return chainCompare(nums,
|
||||
func(a, b int) bool { return a <= b },
|
||||
func(a, b *big.Int) bool { return a.Cmp(b) <= 0 },
|
||||
func(a, b *big.Rat) bool { return a.Cmp(b) <= 0 },
|
||||
func(a, b float64) bool { return a <= b })
|
||||
}
|
||||
|
||||
func eqNum(nums ...vals.Num) bool {
|
||||
return chainCompare(nums, func(pair vals.NumSlice) bool {
|
||||
switch pair := pair.(type) {
|
||||
case []int:
|
||||
return pair[0] == pair[1]
|
||||
case []*big.Int:
|
||||
return pair[0].Cmp(pair[1]) == 0
|
||||
case []*big.Rat:
|
||||
return pair[0].Cmp(pair[1]) == 0
|
||||
case []float64:
|
||||
return pair[0] == pair[1]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
})
|
||||
return chainCompare(nums,
|
||||
func(a, b int) bool { return a == b },
|
||||
func(a, b *big.Int) bool { return a.Cmp(b) == 0 },
|
||||
func(a, b *big.Rat) bool { return a.Cmp(b) == 0 },
|
||||
func(a, b float64) bool { return a == b })
|
||||
}
|
||||
|
||||
func ne(nums ...vals.Num) bool {
|
||||
return chainCompare(nums, func(pair vals.NumSlice) bool {
|
||||
switch pair := pair.(type) {
|
||||
case []int:
|
||||
return pair[0] != pair[1]
|
||||
case []*big.Int:
|
||||
return pair[0].Cmp(pair[1]) != 0
|
||||
case []*big.Rat:
|
||||
return pair[0].Cmp(pair[1]) != 0
|
||||
case []float64:
|
||||
return pair[0] != pair[1]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
})
|
||||
return chainCompare(nums,
|
||||
func(a, b int) bool { return a != b },
|
||||
func(a, b *big.Int) bool { return a.Cmp(b) != 0 },
|
||||
func(a, b *big.Rat) bool { return a.Cmp(b) != 0 },
|
||||
func(a, b float64) bool { return a != b })
|
||||
}
|
||||
|
||||
func gt(nums ...vals.Num) bool {
|
||||
return chainCompare(nums, func(pair vals.NumSlice) bool {
|
||||
switch pair := pair.(type) {
|
||||
case []int:
|
||||
return pair[0] > pair[1]
|
||||
case []*big.Int:
|
||||
return pair[0].Cmp(pair[1]) > 0
|
||||
case []*big.Rat:
|
||||
return pair[0].Cmp(pair[1]) > 0
|
||||
case []float64:
|
||||
return pair[0] > pair[1]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
})
|
||||
return chainCompare(nums,
|
||||
func(a, b int) bool { return a > b },
|
||||
func(a, b *big.Int) bool { return a.Cmp(b) > 0 },
|
||||
func(a, b *big.Rat) bool { return a.Cmp(b) > 0 },
|
||||
func(a, b float64) bool { return a > b })
|
||||
}
|
||||
|
||||
func ge(nums ...vals.Num) bool {
|
||||
return chainCompare(nums, func(pair vals.NumSlice) bool {
|
||||
switch pair := pair.(type) {
|
||||
case []int:
|
||||
return pair[0] >= pair[1]
|
||||
case []*big.Int:
|
||||
return pair[0].Cmp(pair[1]) >= 0
|
||||
case []*big.Rat:
|
||||
return pair[0].Cmp(pair[1]) >= 0
|
||||
case []float64:
|
||||
return pair[0] >= pair[1]
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
})
|
||||
return chainCompare(nums,
|
||||
func(a, b int) bool { return a >= b },
|
||||
func(a, b *big.Int) bool { return a.Cmp(b) >= 0 },
|
||||
func(a, b *big.Rat) bool { return a.Cmp(b) >= 0 },
|
||||
func(a, b float64) bool { return a >= b })
|
||||
}
|
||||
|
||||
func chainCompare(nums []vals.Num, p func(pair vals.NumSlice) bool) bool {
|
||||
func chainCompare(nums []vals.Num,
|
||||
p1 func(a, b int) bool, p2 func(a, b *big.Int) bool,
|
||||
p3 func(a, b *big.Rat) bool, p4 func(a, b float64) bool) bool {
|
||||
|
||||
for i := 0; i < len(nums)-1; i++ {
|
||||
if !p(vals.UnifyNums(nums[i:i+2], 0)) {
|
||||
var r bool
|
||||
switch pair := vals.UnifyNums(nums[i:i+2], 0).(type) {
|
||||
case []int:
|
||||
r = p1(pair[0], pair[1])
|
||||
case []*big.Int:
|
||||
r = p2(pair[0], pair[1])
|
||||
case []*big.Rat:
|
||||
r = p3(pair[0], pair[1])
|
||||
case []float64:
|
||||
r = p4(pair[0], pair[1])
|
||||
}
|
||||
if !r {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -287,13 +248,13 @@ func add(rawNums ...vals.Num) vals.Num {
|
|||
for _, num := range nums {
|
||||
acc.Add(acc, num)
|
||||
}
|
||||
return vals.NormalizeNum(acc)
|
||||
return vals.NormalizeBigInt(acc)
|
||||
case []*big.Rat:
|
||||
acc := big.NewRat(0, 1)
|
||||
for _, num := range nums {
|
||||
acc.Add(acc, num)
|
||||
}
|
||||
return vals.NormalizeNum(acc)
|
||||
return vals.NormalizeBigRat(acc)
|
||||
case []float64:
|
||||
acc := float64(0)
|
||||
for _, num := range nums {
|
||||
|
@ -426,13 +387,13 @@ func mul(rawNums ...vals.Num) vals.Num {
|
|||
for _, num := range nums {
|
||||
acc.Mul(acc, num)
|
||||
}
|
||||
return vals.NormalizeNum(acc)
|
||||
return vals.NormalizeBigInt(acc)
|
||||
case []*big.Rat:
|
||||
acc := big.NewRat(1, 1)
|
||||
for _, num := range nums {
|
||||
acc.Mul(acc, num)
|
||||
}
|
||||
return vals.NormalizeNum(acc)
|
||||
return vals.NormalizeBigRat(acc)
|
||||
case []float64:
|
||||
acc := float64(1)
|
||||
for _, num := range nums {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package eval_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "src.elv.sh/pkg/eval"
|
||||
|
@ -9,6 +12,28 @@ import (
|
|||
. "src.elv.sh/pkg/eval/evaltest"
|
||||
)
|
||||
|
||||
const (
|
||||
zeros = "0000000000000000000"
|
||||
// Values that exceed the range of int64, used for testing BigInt.
|
||||
z = "1" + zeros + "0"
|
||||
z1 = "1" + zeros + "1" // z+1
|
||||
z2 = "1" + zeros + "2" // z+2
|
||||
z3 = "1" + zeros + "3" // z+3
|
||||
zz = "2" + zeros + "0" // 2z
|
||||
zz1 = "2" + zeros + "1" // 2z+1
|
||||
zz2 = "2" + zeros + "2" // 2z+2
|
||||
zz3 = "2" + zeros + "3" // 2z+3
|
||||
)
|
||||
|
||||
func TestNum(t *testing.T) {
|
||||
Test(t,
|
||||
That("num 1").Puts(1),
|
||||
That("num "+z).Puts(bigInt(z)),
|
||||
That("num 1/2").Puts(big.NewRat(1, 2)),
|
||||
That("num (num 1)").Puts(1),
|
||||
)
|
||||
}
|
||||
|
||||
func TestFloat64(t *testing.T) {
|
||||
Test(t,
|
||||
That("float64 1").Puts(1.0),
|
||||
|
@ -16,32 +41,166 @@ func TestFloat64(t *testing.T) {
|
|||
)
|
||||
}
|
||||
|
||||
func TestNumberComparisonCommands(t *testing.T) {
|
||||
func TestNumCmp(t *testing.T) {
|
||||
Test(t,
|
||||
// FixInt
|
||||
That("< 1 2 3").Puts(true),
|
||||
That("< 1 3 2").Puts(false),
|
||||
// BigInt
|
||||
That("< "+args(z1, z2, z3)).Puts(true),
|
||||
That("< "+args(z1, z3, z2)).Puts(false),
|
||||
// BigInt and FixInt
|
||||
That("< "+args("1", z1)).Puts(true),
|
||||
// BigRat
|
||||
That("< 1/4 1/3 1/2").Puts(true),
|
||||
That("< 1/4 1/2 1/3").Puts(false),
|
||||
// BigRat, BigInt and FixInt
|
||||
That("< "+args("1/2", "1", z1)).Puts(true),
|
||||
That("< "+args("1/2", z1, "1")).Puts(false),
|
||||
// Float
|
||||
That("< 1.0 2.0 3.0").Puts(true),
|
||||
That("< 1.0 3.0 2.0").Puts(false),
|
||||
// Float, BigRat and FixInt
|
||||
That("< 1.0 3/2 2").Puts(true),
|
||||
That("< 1.0 2 3/2").Puts(false),
|
||||
|
||||
// Mixing of types not tested for commands below; they share the same
|
||||
// code path as <.
|
||||
|
||||
// FixInt
|
||||
That("<= 1 1 2").Puts(true),
|
||||
That("<= 1 2 1").Puts(false),
|
||||
// BigInt
|
||||
That("<= "+args(z1, z1, z2)).Puts(true),
|
||||
That("<= "+args(z1, z2, z1)).Puts(false),
|
||||
// BigRat
|
||||
That("<= 1/3 1/3 1/2").Puts(true),
|
||||
That("<= 1/3 1/2 1/1").Puts(true),
|
||||
// Float
|
||||
That("<= 1.0 1.0 2.0").Puts(true),
|
||||
That("<= 1.0 2.0 1.0").Puts(false),
|
||||
|
||||
// FixInt
|
||||
That("== 1 1 1").Puts(true),
|
||||
That("== 1 2 1").Puts(false),
|
||||
// BigInt
|
||||
That("== "+args(z1, z1, z1)).Puts(true),
|
||||
That("== "+args(z1, z2, z1)).Puts(false),
|
||||
// BigRat
|
||||
That("== 1/2 1/2 1/2").Puts(true),
|
||||
That("== 1/2 1/3 1/2").Puts(false),
|
||||
// Float
|
||||
That("== 1.0 1.0 1.0").Puts(true),
|
||||
That("== 1.0 2.0 1.0").Puts(false),
|
||||
|
||||
// FixInt
|
||||
That("!= 1 2 1").Puts(true),
|
||||
That("!= 1 1 2").Puts(false),
|
||||
// BigInt
|
||||
That("!= "+args(z1, z2, z1)).Puts(true),
|
||||
That("!= "+args(z1, z1, z2)).Puts(false),
|
||||
// BigRat
|
||||
That("!= 1/2 1/3 1/2").Puts(true),
|
||||
That("!= 1/2 1/2 1/3").Puts(false),
|
||||
// Float
|
||||
That("!= 1.0 2.0 1.0").Puts(true),
|
||||
That("!= 1.0 1.0 2.0").Puts(false),
|
||||
|
||||
// FixInt
|
||||
That("> 3 2 1").Puts(true),
|
||||
That("> 3 1 2").Puts(false),
|
||||
// BigInt
|
||||
That("> "+args(z3, z2, z1)).Puts(true),
|
||||
That("> "+args(z3, z1, z2)).Puts(false),
|
||||
// BigRat
|
||||
That("> 1/2 1/3 1/4").Puts(true),
|
||||
That("> 1/2 1/4 1/3").Puts(false),
|
||||
// Float
|
||||
That("> 3.0 2.0 1.0").Puts(true),
|
||||
That("> 3.0 1.0 2.0").Puts(false),
|
||||
|
||||
// FixInt
|
||||
That(">= 3 3 2").Puts(true),
|
||||
That(">= 3 2 3").Puts(false),
|
||||
// BigInt
|
||||
That(">= "+args(z3, z3, z2)).Puts(true),
|
||||
That(">= "+args(z3, z2, z3)).Puts(false),
|
||||
// BigRat
|
||||
That(">= 1/2 1/2 1/3").Puts(true),
|
||||
That(">= 1/2 1/3 1/2").Puts(false),
|
||||
// Float
|
||||
That(">= 3.0 3.0 2.0").Puts(true),
|
||||
That(">= 3.0 2.0 3.0").Puts(false),
|
||||
)
|
||||
}
|
||||
|
||||
func TestArithmeticCommands(t *testing.T) {
|
||||
Test(t,
|
||||
// TODO test more edge cases
|
||||
// No argument
|
||||
That("+").Puts(0),
|
||||
// FixInt
|
||||
That("+ 233100 233").Puts(233333),
|
||||
That("- 233333 233100").Puts(233),
|
||||
// BigInt
|
||||
That("+ "+args(z, z1)).Puts(bigInt(zz1)),
|
||||
// BigInt and FixInt
|
||||
That("+ 1 2 "+z).Puts(bigInt(z3)),
|
||||
// BigRat
|
||||
That("+ 1/2 1/3 1/4").Puts(big.NewRat(13, 12)),
|
||||
// BigRat, BigInt and FixInt
|
||||
That("+ 1/2 1/2 1 "+z).Puts(bigInt(z2)),
|
||||
// Float
|
||||
That("+ 0.5 0.25 1.0").Puts(1.75),
|
||||
// Float and other types
|
||||
That("+ 0.5 1/4 1").Puts(1.75),
|
||||
|
||||
// Mixing of types not tested for commands below; they share the same
|
||||
// code path as +.
|
||||
|
||||
// One argument - negation
|
||||
That("- 233").Puts(-233),
|
||||
That("* 353 661").Puts(233333),
|
||||
That("- "+z).Puts(bigInt("-"+z)),
|
||||
That("- 1/2").Puts(big.NewRat(-1, 2)),
|
||||
That("- 1.0").Puts(-1.0),
|
||||
// FixInt
|
||||
That("- 20 10 2").Puts(8),
|
||||
// BigInt
|
||||
That("- "+args(zz3, z1)).Puts(bigInt(z2)),
|
||||
// Float
|
||||
That("- 2.0 1.0 0.5").Puts(0.5),
|
||||
|
||||
// No argument
|
||||
That("*").Puts(1),
|
||||
// FixInt
|
||||
That("* 2 7 4").Puts(56),
|
||||
// BigInt
|
||||
That("* 2 "+z1).Puts(bigInt(zz2)),
|
||||
// Float
|
||||
That("* 2.0 0.5 1.75").Puts(1.75),
|
||||
// 0 * non-infinity
|
||||
That("* 0 1/2 1.0").Puts(0),
|
||||
// 0 * infinity
|
||||
That("* 0 +Inf").Puts(math.NaN()),
|
||||
|
||||
// One argument - inversion
|
||||
That("/ 2").Puts(big.NewRat(1, 2)),
|
||||
That("/ "+z).Puts(bigRat("1/"+z)),
|
||||
That("/ 2.0").Puts(0.5),
|
||||
// FixInt
|
||||
That("/ 233333 353").Puts(661),
|
||||
That("/ 3 4 2").Puts(big.NewRat(3, 8)),
|
||||
// BigInt
|
||||
That("/ "+args(zz, z)).Puts(2),
|
||||
That("/ "+args(zz, "2")).Puts(bigInt(z)),
|
||||
That("/ "+args(z1, z)).Puts(bigRat(z1+"/"+z)),
|
||||
// Float
|
||||
That("/ 1.0 2.0 4.0").Puts(0.125),
|
||||
// 0 / non-zero
|
||||
That("/ 0 1/2 0.1").Puts(0),
|
||||
// anything / 0
|
||||
That("/ 0 0").Throws(ErrDivideByZero, "/ 0 0"),
|
||||
That("/ 1 0").Throws(ErrDivideByZero, "/ 1 0"),
|
||||
That("/ 1.0 0").Throws(ErrDivideByZero, "/ 1.0 0"),
|
||||
|
||||
That("% 23 7").Puts(2),
|
||||
That("% 1 0").Throws(ErrDivideByZero, "% 1 0"),
|
||||
)
|
||||
|
@ -57,3 +216,23 @@ func TestRandint(t *testing.T) {
|
|||
That("randint 1 2 3").Throws(ErrorWithType(errs.ArityMismatch{}), "randint 1 2 3"),
|
||||
)
|
||||
}
|
||||
|
||||
func bigInt(s string) *big.Int {
|
||||
z, ok := new(big.Int).SetString(s, 0)
|
||||
if !ok {
|
||||
panic("cannot parse as big int: " + s)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func bigRat(s string) *big.Rat {
|
||||
z, ok := new(big.Rat).SetString(s)
|
||||
if !ok {
|
||||
panic("cannot parse as big rat: " + s)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func args(s ...string) string {
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package vals
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
. "src.elv.sh/pkg/tt"
|
||||
|
@ -33,8 +34,16 @@ func (rconcatter) RConcat(lhs interface{}) (interface{}, error) {
|
|||
func TestConcat(t *testing.T) {
|
||||
Test(t, Fn("Concat", Concat), Table{
|
||||
Args("foo", "bar").Rets("foobar", nil),
|
||||
// string+number
|
||||
Args("foo", 2).Rets("foo2", nil),
|
||||
Args("foo", bigInt(z)).Rets("foo"+z, nil),
|
||||
Args("foo", big.NewRat(1, 2)).Rets("foo1/2", nil),
|
||||
Args("foo", 2.0).Rets("foo2.0", nil),
|
||||
// number+string
|
||||
Args(2, "foo").Rets("2foo", nil),
|
||||
Args(bigInt(z), "foo").Rets(z+"foo", nil),
|
||||
Args(big.NewRat(1, 2), "foo").Rets("1/2foo", nil),
|
||||
Args(2.0, "foo").Rets("2.0foo", nil),
|
||||
|
||||
// LHS implements Concatter and succeeds
|
||||
Args(concatter{}, "bar").Rets("concatter bar", nil),
|
||||
|
|
|
@ -86,8 +86,6 @@ func ScanToGo(src interface{}, ptr interface{}) error {
|
|||
*ptr = r
|
||||
}
|
||||
return err
|
||||
case Scanner:
|
||||
return ptr.ScanElvish(src)
|
||||
default:
|
||||
// Do a generic `*ptr = src` via reflection
|
||||
ptrType := TypeOf(ptr)
|
||||
|
@ -103,11 +101,6 @@ func ScanToGo(src interface{}, ptr interface{}) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Scanner is implemented by types that can scan an Elvish value into itself.
|
||||
type Scanner interface {
|
||||
ScanElvish(interface{}) error
|
||||
}
|
||||
|
||||
// FromGo converts a Go value to an Elvish value. Most types are returned as
|
||||
// is, but exact numerical types are normalized to one of int, *big.Int and
|
||||
// *big.Rat, using the small representation that can hold the value, and runes
|
||||
|
@ -155,12 +148,8 @@ func elvToFloat(arg interface{}) (float64, error) {
|
|||
|
||||
func elvToInt(arg interface{}) (int, error) {
|
||||
switch arg := arg.(type) {
|
||||
case float64:
|
||||
i := int(arg)
|
||||
if float64(i) != arg {
|
||||
return 0, errMustBeInteger
|
||||
}
|
||||
return i, nil
|
||||
case int:
|
||||
return arg, nil
|
||||
case string:
|
||||
num, err := strconv.ParseInt(arg, 0, 0)
|
||||
if err == nil {
|
||||
|
@ -172,6 +161,21 @@ func elvToInt(arg interface{}) (int, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func elvToNum(arg interface{}) (Num, error) {
|
||||
switch arg := arg.(type) {
|
||||
case int, *big.Int, *big.Rat, float64:
|
||||
return arg, nil
|
||||
case string:
|
||||
n := ParseNum(arg)
|
||||
if n == nil {
|
||||
return 0, cannotParseAs{"number", Repr(arg, -1)}
|
||||
}
|
||||
return n, nil
|
||||
default:
|
||||
return 0, errMustBeNumber
|
||||
}
|
||||
}
|
||||
|
||||
func elvToRune(arg interface{}) (rune, error) {
|
||||
ss, ok := arg.(string)
|
||||
if !ok {
|
||||
|
@ -187,18 +191,3 @@ func elvToRune(arg interface{}) (rune, error) {
|
|||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func elvToNum(arg interface{}) (Num, error) {
|
||||
switch arg := arg.(type) {
|
||||
case int, *big.Int, *big.Rat, float64:
|
||||
return arg, nil
|
||||
case string:
|
||||
n := ParseNum(arg)
|
||||
if n == nil {
|
||||
return 0, cannotParseAs{"number", Repr(arg, -1)}
|
||||
}
|
||||
return n, nil
|
||||
default:
|
||||
return 0, errMustBeNumber
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package vals
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
|
@ -22,33 +23,70 @@ func scanToGo2(src interface{}, dstInit interface{}) (interface{}, error) {
|
|||
|
||||
func TestScanToGo(t *testing.T) {
|
||||
Test(t, Fn("ScanToGo", scanToGo2), Table{
|
||||
// int
|
||||
Args("12", 0).Rets(12),
|
||||
Args("0x12", 0).Rets(0x12),
|
||||
Args(12.0, 0).Rets(12),
|
||||
Args("23", 0.0).Rets(23.0),
|
||||
Args("0x23", 0.0).Rets(float64(0x23)),
|
||||
Args("x", ' ').Rets('x'),
|
||||
Args("foo", "").Rets("foo"),
|
||||
Args(someType{"foo"}, someType{}).Rets(someType{"foo"}),
|
||||
Args(nil, nil).Rets(nil),
|
||||
|
||||
Args(12.0, 0).Rets(0, errMustBeInteger),
|
||||
Args(0.5, 0).Rets(0, errMustBeInteger),
|
||||
Args("x", someType{}).Rets(Any, wrongType{"!!vals.someType", "string"}),
|
||||
Args(someType{}, 0).Rets(Any, errMustBeInteger),
|
||||
Args("x", 0).Rets(Any, cannotParseAs{"integer", "x"}),
|
||||
|
||||
// float64
|
||||
Args("23", 0.0).Rets(23.0),
|
||||
Args("0x23", 0.0).Rets(float64(0x23)),
|
||||
Args(someType{}, 0.0).Rets(Any, errMustBeNumber),
|
||||
Args("x", 0.0).Rets(Any, cannotParseAs{"number", "x"}),
|
||||
|
||||
// Num is tested below
|
||||
|
||||
// rune
|
||||
Args("x", ' ').Rets('x'),
|
||||
Args(someType{}, ' ').Rets(Any, errMustBeString),
|
||||
Args("\xc3\x28", ' ').Rets(Any, errMustBeValidUTF8), // Invalid UTF8
|
||||
Args("ab", ' ').Rets(Any, errMustHaveSingleRune),
|
||||
|
||||
// Other types don't undergo any conversion, as long as the types match
|
||||
Args("foo", "").Rets("foo"),
|
||||
Args(someType{"foo"}, someType{}).Rets(someType{"foo"}),
|
||||
Args(nil, nil).Rets(nil),
|
||||
Args("x", someType{}).Rets(Any, wrongType{"!!vals.someType", "string"}),
|
||||
})
|
||||
}
|
||||
|
||||
func scanToGoNum(src interface{}) (Num, error) {
|
||||
var n Num
|
||||
err := ScanToGo(src, &n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func TestScanToGoNum(t *testing.T) {
|
||||
Test(t, Fn("ScanToGo", scanToGoNum), Table{
|
||||
// Strings are automatically converted
|
||||
Args("12").Rets(12),
|
||||
Args(z).Rets(bigInt(z)),
|
||||
Args("1/2").Rets(big.NewRat(1, 2)),
|
||||
Args("12.0").Rets(12.0),
|
||||
// Already numbers
|
||||
Args(12).Rets(12),
|
||||
Args(bigInt(z)).Rets(bigInt(z)),
|
||||
Args(big.NewRat(1, 2)).Rets(big.NewRat(1, 2)),
|
||||
Args(12.0).Rets(12.0),
|
||||
})
|
||||
}
|
||||
|
||||
func TestFromGo(t *testing.T) {
|
||||
Test(t, Fn("FromGo", FromGo), Table{
|
||||
Args(12).Rets(12),
|
||||
Args(1.5).Rets(1.5),
|
||||
// BigInt -> int, when in range
|
||||
Args(bigInt(z)).Rets(bigInt(z)),
|
||||
Args(big.NewInt(100)).Rets(100),
|
||||
// BigRat -> BigInt or int, when denominator is 1
|
||||
Args(bigRat(z1 + "/" + z)).Rets(bigRat(z1 + "/" + z)),
|
||||
Args(bigRat(z + "/1")).Rets(bigInt(z)),
|
||||
Args(bigRat("2/1")).Rets(2),
|
||||
// rune -> string
|
||||
Args('x').Rets("x"),
|
||||
|
||||
// Other types don't undergo any conversion
|
||||
Args(nil).Rets(nil),
|
||||
Args(someType{"foo"}).Rets(someType{"foo"}),
|
||||
})
|
||||
|
|
|
@ -8,27 +8,27 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
// Num is a stand-in type for int, *big.Int, *big.Rat or float64. This type
|
||||
// doesn't offer type safety, but is useful as a marker.
|
||||
type Num interface{}
|
||||
|
||||
// NumSlice is a stand-in type for []int, []*big.Int, []*big.Rat or []float64.
|
||||
// This type doesn't offer type safety, but is useful as a marker.
|
||||
type NumSlice interface{}
|
||||
|
||||
// ParseNum parses a string into a suitable number type. If the string does not
|
||||
// represent a valid number, it returns nil.
|
||||
func ParseNum(s string) Num {
|
||||
b := []byte(s)
|
||||
if strings.ContainsRune(s, '/') {
|
||||
// Parse as big.Rat
|
||||
var r big.Rat
|
||||
if r.UnmarshalText(b) == nil {
|
||||
return &r
|
||||
if z, ok := new(big.Rat).SetString(s); ok {
|
||||
return NormalizeBigRat(z)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Try parsing as big.Int
|
||||
z := &big.Int{}
|
||||
if z.UnmarshalText(b) == nil {
|
||||
if i, ok := getFixInt(z); ok {
|
||||
return i
|
||||
}
|
||||
return z
|
||||
if z, ok := new(big.Int).SetString(s, 0); ok {
|
||||
return NormalizeBigInt(z)
|
||||
}
|
||||
// Try parsing as float64
|
||||
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||
|
@ -37,9 +37,11 @@ func ParseNum(s string) Num {
|
|||
return nil
|
||||
}
|
||||
|
||||
// NumType represents a number type.
|
||||
type NumType uint8
|
||||
|
||||
// Precedence used for unifying number types.
|
||||
// Possible values for NumType, sorted in the order of implicit conversion
|
||||
// (lower types can be implicitly converted to higher types).
|
||||
const (
|
||||
FixInt NumType = iota
|
||||
BigInt
|
||||
|
@ -47,6 +49,9 @@ const (
|
|||
Float64
|
||||
)
|
||||
|
||||
// UnifyNums unifies the given slice of numbers into the same type, converting
|
||||
// those with lower NumType to the higest NumType present in the slice. The typ
|
||||
// argument can be used to force the minimum NumType.
|
||||
func UnifyNums(nums []Num, typ NumType) NumSlice {
|
||||
for _, num := range nums {
|
||||
if t := getNumType(num); t > typ {
|
||||
|
@ -133,28 +138,26 @@ func getNumType(n Num) NumType {
|
|||
}
|
||||
}
|
||||
|
||||
func NormalizeNum(n Num) Num {
|
||||
switch n := n.(type) {
|
||||
case int:
|
||||
return n
|
||||
case *big.Int:
|
||||
// NormalizeBigInt converts a big.Int to an int if it is within the range of
|
||||
// int. Otherwise it returns n as is.
|
||||
func NormalizeBigInt(z *big.Int) Num {
|
||||
if i, ok := getFixInt(z); ok {
|
||||
return i
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
// NormalizeBigRat converts a big.Rat to a big.Int (or an int if within the
|
||||
// range) if its denominator is 1.
|
||||
func NormalizeBigRat(z *big.Rat) Num {
|
||||
if z.IsInt() {
|
||||
n := z.Num()
|
||||
if i, ok := getFixInt(n); ok {
|
||||
return i
|
||||
}
|
||||
return n
|
||||
case *big.Rat:
|
||||
if n.IsInt() {
|
||||
if i, ok := getFixInt(n.Num()); ok {
|
||||
return i
|
||||
}
|
||||
return n
|
||||
}
|
||||
return n
|
||||
case float64:
|
||||
return n
|
||||
default:
|
||||
panic("invalid num type" + fmt.Sprintf("%T", n))
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func getFixInt(z *big.Int) (int, bool) {
|
||||
|
|
89
pkg/eval/vals/num_test.go
Normal file
89
pkg/eval/vals/num_test.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package vals
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
. "src.elv.sh/pkg/tt"
|
||||
)
|
||||
|
||||
// Test utilities.
|
||||
|
||||
const (
|
||||
zeros = "0000000000000000000"
|
||||
// Values that exceed the range of int64, used for testing BigInt.
|
||||
z = "1" + zeros + "0"
|
||||
z1 = "1" + zeros + "1" // z+1
|
||||
z2 = "1" + zeros + "2" // z+2
|
||||
z3 = "1" + zeros + "3" // z+3
|
||||
zz = "2" + zeros + "0" // 2z
|
||||
zz1 = "2" + zeros + "1" // 2z+1
|
||||
zz2 = "2" + zeros + "2" // 2z+2
|
||||
zz3 = "2" + zeros + "3" // 2z+3
|
||||
)
|
||||
|
||||
func TestParseNum(t *testing.T) {
|
||||
Test(t, Fn("ParseNum", ParseNum), Table{
|
||||
Args("1").Rets(1),
|
||||
|
||||
Args(z).Rets(bigInt(z)),
|
||||
|
||||
Args("1/2").Rets(big.NewRat(1, 2)),
|
||||
Args("2/1").Rets(2),
|
||||
Args(z + "/1").Rets(bigInt(z)),
|
||||
|
||||
Args("1.0").Rets(1.0),
|
||||
Args("1e-5").Rets(1e-5),
|
||||
|
||||
Args("x").Rets(nil),
|
||||
Args("x/y").Rets(nil),
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnifyNums(t *testing.T) {
|
||||
Test(t, Fn("UnifyNums", UnifyNums), Table{
|
||||
Args([]Num{1, 2, 3, 4}, FixInt).
|
||||
Rets([]int{1, 2, 3, 4}),
|
||||
|
||||
Args([]Num{1, 2, 3, bigInt(z)}, FixInt).
|
||||
Rets([]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3), bigInt(z)}),
|
||||
|
||||
Args([]Num{1, 2, 3, big.NewRat(1, 2)}, FixInt).
|
||||
Rets([]*big.Rat{
|
||||
big.NewRat(1, 1), big.NewRat(2, 1),
|
||||
big.NewRat(3, 1), big.NewRat(1, 2)}),
|
||||
Args([]Num{1, 2, bigInt(z), big.NewRat(1, 2)}, FixInt).
|
||||
Rets([]*big.Rat{
|
||||
big.NewRat(1, 1), big.NewRat(2, 1), bigRat(z), big.NewRat(1, 2)}),
|
||||
|
||||
Args([]Num{1, 2, 3, 4.0}, FixInt).
|
||||
Rets([]float64{1, 2, 3, 4}),
|
||||
Args([]Num{1, 2, big.NewRat(1, 2), 4.0}, FixInt).
|
||||
Rets([]float64{1, 2, 0.5, 4}),
|
||||
Args([]Num{1, 2, big.NewInt(3), 4.0}, FixInt).
|
||||
Rets([]float64{1, 2, 3, 4}),
|
||||
Args([]Num{1, 2, bigInt(z), 4.0}, FixInt).
|
||||
Rets([]float64{1, 2, math.Inf(1), 4}),
|
||||
|
||||
Args([]Num{1, 2, 3, 4}, BigInt).
|
||||
Rets([]*big.Int{
|
||||
big.NewInt(1), big.NewInt(2), big.NewInt(3), big.NewInt(4)}),
|
||||
})
|
||||
}
|
||||
|
||||
func bigInt(s string) *big.Int {
|
||||
z, ok := new(big.Int).SetString(s, 0)
|
||||
if !ok {
|
||||
panic("cannot parse as big int: " + s)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
||||
func bigRat(s string) *big.Rat {
|
||||
z, ok := new(big.Rat).SetString(s)
|
||||
if !ok {
|
||||
panic("cannot parse as big rat: " + s)
|
||||
}
|
||||
return z
|
||||
}
|
|
@ -2,6 +2,7 @@ package vals
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
@ -19,17 +20,27 @@ func repr(a interface{}) string { return Repr(a, NoPretty) }
|
|||
func TestRepr(t *testing.T) {
|
||||
Test(t, Fn("repr", repr), Table{
|
||||
Args(nil).Rets("$nil"),
|
||||
|
||||
Args(false).Rets("$false"),
|
||||
Args(true).Rets("$true"),
|
||||
|
||||
Args("foo").Rets("foo"),
|
||||
|
||||
Args(1).Rets("(num 1)"),
|
||||
Args(bigInt(z)).Rets("(num " + z + ")"),
|
||||
Args(big.NewRat(1, 2)).Rets("(num 1/2)"),
|
||||
Args(1.0).Rets("(num 1.0)"),
|
||||
Args(1e10).Rets("(num 10000000000.0)"),
|
||||
|
||||
Args(os.Stdin).Rets(
|
||||
fmt.Sprintf("<file{%s %d}>", os.Stdin.Name(), os.Stdin.Fd())),
|
||||
|
||||
Args(EmptyList).Rets("[]"),
|
||||
Args(MakeList("foo", "bar")).Rets("[foo bar]"),
|
||||
|
||||
Args(EmptyMap).Rets("[&]"),
|
||||
Args(MakeMap("foo", "bar")).Rets("[&foo=bar]"),
|
||||
|
||||
Args(reprer{}).Rets("<reprer>"),
|
||||
Args(nonReprer{}).Rets("<unknown {}>"),
|
||||
})
|
||||
|
|
|
@ -12,9 +12,11 @@ func TestToString(t *testing.T) {
|
|||
// string
|
||||
Args("a").Rets("a"),
|
||||
|
||||
Args(1).Rets("1"),
|
||||
|
||||
// float64
|
||||
Args(42.0).Rets("42.0"),
|
||||
Args(0.1).Rets("0.1"),
|
||||
Args(42.0).Rets("42.0"),
|
||||
// Whole numbers with more than 14 digits and trailing 0 are printed in
|
||||
// scientific notation.
|
||||
Args(1e13).Rets("10000000000000.0"),
|
||||
|
|
Loading…
Reference in New Issue
Block a user