mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
596 lines
12 KiB
Go
596 lines
12 KiB
Go
package eval
|
|
|
|
import (
|
|
"math"
|
|
"math/big"
|
|
"math/rand"
|
|
|
|
"src.elv.sh/pkg/eval/errs"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
)
|
|
|
|
// Numerical operations.
|
|
|
|
//elvdoc:fn rand
|
|
//
|
|
// ```elvish
|
|
// rand
|
|
// ```
|
|
//
|
|
// Output a pseudo-random number in the interval [0, 1). Example:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> rand
|
|
// ▶ 0.17843564133528436
|
|
// ```
|
|
|
|
func init() {
|
|
addBuiltinFns(map[string]interface{}{
|
|
// Constructor
|
|
"float64": toFloat64,
|
|
"num": num,
|
|
"exact-num": exactNum,
|
|
|
|
// Comparison
|
|
"<": lt,
|
|
"<=": le,
|
|
"==": eqNum,
|
|
"!=": ne,
|
|
">": gt,
|
|
">=": ge,
|
|
|
|
// Arithmetic
|
|
"+": add,
|
|
"-": sub,
|
|
"*": mul,
|
|
// Also handles cd /
|
|
"/": slash,
|
|
"%": rem,
|
|
|
|
// Random
|
|
"rand": rand.Float64,
|
|
"randint": randint,
|
|
})
|
|
}
|
|
|
|
//elvdoc:fn num
|
|
//
|
|
// ```elvish
|
|
// num $string-or-number
|
|
// ```
|
|
//
|
|
// Constructs a [typed number](./language.html#number).
|
|
//
|
|
// If the argument is a string, this command outputs the typed number the
|
|
// argument represents, or raises an exception if the argument is not a valid
|
|
// representation of a number. If the argument is already a typed number, this
|
|
// command outputs it as is.
|
|
//
|
|
// This command is usually not needed for working with numbers; see the
|
|
// discussion of [numerical commands](#numerical-commands).
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> num 10
|
|
// ▶ (num 10)
|
|
// ~> num 0x10
|
|
// ▶ (num 16)
|
|
// ~> num 1/12
|
|
// ▶ (num 1/12)
|
|
// ~> num 3.14
|
|
// ▶ (num 3.14)
|
|
// ~> num (num 10)
|
|
// ▶ (num 10)
|
|
// ```
|
|
|
|
func num(n vals.Num) vals.Num {
|
|
// Conversion is actually handled in vals/conversion.go.
|
|
return n
|
|
}
|
|
|
|
//elvdoc:fn exact-num
|
|
//
|
|
// ```elvish
|
|
// exact-num $string-or-number
|
|
// ```
|
|
//
|
|
// Coerces the argument to an exact number. If the argument is infinity or NaN,
|
|
// an exception is thrown.
|
|
//
|
|
// If the argument is a string, it is converted to a typed number first. If the
|
|
// argument is already an exact number, it is returned as is.
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> exact-num (num 0.125)
|
|
// ▶ (num 1/8)
|
|
// ~> exact-num 0.125
|
|
// ▶ (num 1/8)
|
|
// ~> exact-num (num 1)
|
|
// ▶ (num 1)
|
|
// ```
|
|
//
|
|
// Beware that seemingly simple fractions that can't be represented precisely in
|
|
// binary can result in the denominator being a very large power of 2:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> exact-num 0.1
|
|
// ▶ (num 3602879701896397/36028797018963968)
|
|
// ```
|
|
|
|
func exactNum(n vals.Num) (vals.Num, error) {
|
|
if f, ok := n.(float64); ok {
|
|
r := new(big.Rat).SetFloat64(f)
|
|
if r == nil {
|
|
return nil, errs.BadValue{What: "argument here",
|
|
Valid: "finite float", Actual: vals.ToString(f)}
|
|
}
|
|
return r, nil
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
//elvdoc:fn float64
|
|
//
|
|
// ```elvish
|
|
// float64 $string-or-number
|
|
// ```
|
|
//
|
|
// Constructs a floating-point number.
|
|
//
|
|
// This command is deprecated; use [`num`](#num) instead.
|
|
|
|
func toFloat64(f float64) float64 {
|
|
return f
|
|
}
|
|
|
|
//elvdoc:fn < <= == != > >= {#num-cmp}
|
|
//
|
|
// ```elvish
|
|
// < $number... # less
|
|
// <= $number... # less or equal
|
|
// == $number... # equal
|
|
// != $number... # not equal
|
|
// > $number... # greater
|
|
// >= $number... # greater or equal
|
|
// ```
|
|
//
|
|
// Number comparisons. All of them accept an arbitrary number of arguments:
|
|
//
|
|
// 1. When given fewer than two arguments, all output `$true`.
|
|
//
|
|
// 2. When given two arguments, output whether the two arguments satisfy the named
|
|
// relationship.
|
|
//
|
|
// 3. When given more than two arguments, output whether every adjacent pair of
|
|
// numbers satisfy the named relationship.
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> == 3 3.0
|
|
// ▶ $true
|
|
// ~> < 3 4
|
|
// ▶ $true
|
|
// ~> < 3 4 10
|
|
// ▶ $true
|
|
// ~> < 6 9 1
|
|
// ▶ $false
|
|
// ```
|
|
//
|
|
// As a consequence of rule 3, the `!=` command outputs `$true` as long as any
|
|
// _adjacent_ pair of numbers are not equal, even if some numbers that are not
|
|
// adjacent are equal:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> != 5 5 4
|
|
// ▶ $false
|
|
// ~> != 5 6 5
|
|
// ▶ $true
|
|
// ```
|
|
|
|
func lt(nums ...vals.Num) bool {
|
|
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(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(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(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(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(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,
|
|
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++ {
|
|
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
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
//elvdoc:fn + {#add}
|
|
//
|
|
// ```elvish
|
|
// + $num...
|
|
// ```
|
|
//
|
|
// Outputs the sum of all arguments, or 0 when there are no arguments.
|
|
//
|
|
// This command is [exactness-preserving](#exactness-preserving).
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> + 5 2 7
|
|
// ▶ (num 14)
|
|
// ~> + 1/2 1/3 1/4
|
|
// ▶ (num 13/12)
|
|
// ~> + 1/2 0.5
|
|
// ▶ (num 1.0)
|
|
// ```
|
|
|
|
func add(rawNums ...vals.Num) vals.Num {
|
|
nums := vals.UnifyNums(rawNums, vals.BigInt)
|
|
switch nums := nums.(type) {
|
|
case []*big.Int:
|
|
acc := big.NewInt(0)
|
|
for _, num := range nums {
|
|
acc.Add(acc, num)
|
|
}
|
|
return vals.NormalizeBigInt(acc)
|
|
case []*big.Rat:
|
|
acc := big.NewRat(0, 1)
|
|
for _, num := range nums {
|
|
acc.Add(acc, num)
|
|
}
|
|
return vals.NormalizeBigRat(acc)
|
|
case []float64:
|
|
acc := float64(0)
|
|
for _, num := range nums {
|
|
acc += num
|
|
}
|
|
return acc
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
//elvdoc:fn - {#sub}
|
|
//
|
|
// ```elvish
|
|
// - $x-num $y-num...
|
|
// ```
|
|
//
|
|
// Outputs the result of substracting from `$x-num` all the `$y-num`s, working
|
|
// from left to right. When no `$y-num` is given, outputs the negation of
|
|
// `$x-num` instead (in other words, `- $x-num` is equivalent to `- 0 $x-num`).
|
|
//
|
|
// This command is [exactness-preserving](#exactness-preserving).
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> - 5
|
|
// ▶ (num -5)
|
|
// ~> - 5 2
|
|
// ▶ (num 3)
|
|
// ~> - 5 2 7
|
|
// ▶ (num -4)
|
|
// ~> - 1/2 1/3
|
|
// ▶ (num 1/6)
|
|
// ~> - 1/2 0.3
|
|
// ▶ (num 0.2)
|
|
// ~> - 10
|
|
// ▶ (num -10)
|
|
// ```
|
|
|
|
func sub(rawNums ...vals.Num) (vals.Num, error) {
|
|
if len(rawNums) == 0 {
|
|
return nil, errs.ArityMismatch{
|
|
What: "arguments here",
|
|
ValidLow: 1, ValidHigh: -1, Actual: 0,
|
|
}
|
|
}
|
|
|
|
nums := vals.UnifyNums(rawNums, vals.BigInt)
|
|
switch nums := nums.(type) {
|
|
case []*big.Int:
|
|
acc := &big.Int{}
|
|
if len(nums) == 1 {
|
|
acc.Neg(nums[0])
|
|
return acc, nil
|
|
}
|
|
acc.Set(nums[0])
|
|
for _, num := range nums[1:] {
|
|
acc.Sub(acc, num)
|
|
}
|
|
return acc, nil
|
|
case []*big.Rat:
|
|
acc := &big.Rat{}
|
|
if len(nums) == 1 {
|
|
acc.Neg(nums[0])
|
|
return acc, nil
|
|
}
|
|
acc.Set(nums[0])
|
|
for _, num := range nums[1:] {
|
|
acc.Sub(acc, num)
|
|
}
|
|
return acc, nil
|
|
case []float64:
|
|
if len(nums) == 1 {
|
|
return -nums[0], nil
|
|
}
|
|
acc := nums[0]
|
|
for _, num := range nums[1:] {
|
|
acc -= num
|
|
}
|
|
return acc, nil
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
//elvdoc:fn * {#mul}
|
|
//
|
|
// ```elvish
|
|
// * $num...
|
|
// ```
|
|
//
|
|
// Outputs the product of all arguments, or 1 when there are no arguments.
|
|
//
|
|
// This command is [exactness-preserving](#exactness-preserving). Additionally,
|
|
// when any argument is exact 0 and no other argument is a floating-point
|
|
// infinity, the result is exact 0.
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> * 2 5 7
|
|
// ▶ (num 70)
|
|
// ~> * 1/2 0.5
|
|
// ▶ (num 0.25)
|
|
// ~> * 0 0.5
|
|
// ▶ (num 0)
|
|
// ```
|
|
|
|
func mul(rawNums ...vals.Num) vals.Num {
|
|
hasExact0 := false
|
|
hasInf := false
|
|
for _, num := range rawNums {
|
|
if num == 0 {
|
|
hasExact0 = true
|
|
}
|
|
if f, ok := num.(float64); ok && math.IsInf(f, 0) {
|
|
hasInf = true
|
|
break
|
|
}
|
|
}
|
|
if hasExact0 && !hasInf {
|
|
return 0
|
|
}
|
|
|
|
nums := vals.UnifyNums(rawNums, vals.BigInt)
|
|
switch nums := nums.(type) {
|
|
case []*big.Int:
|
|
acc := big.NewInt(1)
|
|
for _, num := range nums {
|
|
acc.Mul(acc, num)
|
|
}
|
|
return vals.NormalizeBigInt(acc)
|
|
case []*big.Rat:
|
|
acc := big.NewRat(1, 1)
|
|
for _, num := range nums {
|
|
acc.Mul(acc, num)
|
|
}
|
|
return vals.NormalizeBigRat(acc)
|
|
case []float64:
|
|
acc := float64(1)
|
|
for _, num := range nums {
|
|
acc *= num
|
|
}
|
|
return acc
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
//elvdoc:fn / {#div}
|
|
//
|
|
// ```elvish
|
|
// / $x-num $y-num...
|
|
// ```
|
|
//
|
|
// Outputs the result of dividing `$x-num` with all the `$y-num`s, working from
|
|
// left to right. When no `$y-num` is given, outputs the reciprocal of `$x-num`
|
|
// instead (in other words, `/ $y-num` is equivalent to `/ 1 $y-num`).
|
|
//
|
|
// Dividing by exact 0 raises an exception. Dividing by inexact 0 results with
|
|
// either infinity or NaN according to floating-point semantics.
|
|
//
|
|
// This command is [exactness-preserving](#exactness-preserving). Additionally,
|
|
// when `$x-num` is exact 0 and no `$y-num` is exact 0, the result is exact 0.
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> / 2
|
|
// ▶ (num 1/2)
|
|
// ~> / 2.0
|
|
// ▶ (num 0.5)
|
|
// ~> / 10 5
|
|
// ▶ (num 2)
|
|
// ~> / 2 5
|
|
// ▶ (num 2/5)
|
|
// ~> / 2 5 7
|
|
// ▶ (num 2/35)
|
|
// ~> / 0 1.0
|
|
// ▶ (num 0)
|
|
// ~> / 2 0
|
|
// Exception: bad value: divisor must be number other than exact 0, but is exact 0
|
|
// [tty 6], line 1: / 2 0
|
|
// ~> / 2 0.0
|
|
// ▶ (num +Inf)
|
|
// ```
|
|
//
|
|
// When given no argument, this command is equivalent to `cd /`, due to the
|
|
// implicit cd feature. (The implicit cd feature will probably change to avoid
|
|
// this oddity).
|
|
|
|
func slash(fm *Frame, args ...vals.Num) error {
|
|
if len(args) == 0 {
|
|
// cd /
|
|
return fm.Evaler.Chdir("/")
|
|
}
|
|
// Division
|
|
result, err := div(args...)
|
|
if err == nil {
|
|
fm.OutputChan() <- vals.FromGo(result)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// ErrDivideByZero is thrown when attempting to divide by zero.
|
|
var ErrDivideByZero = errs.BadValue{
|
|
What: "divisor", Valid: "number other than exact 0", Actual: "exact 0"}
|
|
|
|
func div(rawNums ...vals.Num) (vals.Num, error) {
|
|
for _, num := range rawNums[1:] {
|
|
if num == 0 {
|
|
return nil, ErrDivideByZero
|
|
}
|
|
}
|
|
if rawNums[0] == 0 {
|
|
return 0, nil
|
|
}
|
|
nums := vals.UnifyNums(rawNums, vals.BigRat)
|
|
switch nums := nums.(type) {
|
|
case []*big.Rat:
|
|
acc := &big.Rat{}
|
|
acc.Set(nums[0])
|
|
if len(nums) == 1 {
|
|
acc.Inv(acc)
|
|
return acc, nil
|
|
}
|
|
for _, num := range nums[1:] {
|
|
acc.Quo(acc, num)
|
|
}
|
|
return acc, nil
|
|
case []float64:
|
|
acc := nums[0]
|
|
if len(nums) == 1 {
|
|
return 1 / acc, nil
|
|
}
|
|
for _, num := range nums[1:] {
|
|
acc /= num
|
|
}
|
|
return acc, nil
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
|
|
//elvdoc:fn % {#rem}
|
|
//
|
|
// ```elvish
|
|
// % $x $y
|
|
// ```
|
|
//
|
|
// Output the remainder after dividing `$x` by `$y`. The result has the same
|
|
// sign as `$x`. Both must be integers that can represented in a machine word
|
|
// (this limit may be lifted in future).
|
|
//
|
|
// Examples:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> % 10 3
|
|
// ▶ 1
|
|
// ~> % -10 3
|
|
// ▶ -1
|
|
// ~> % 10 -3
|
|
// ▶ 1
|
|
// ```
|
|
|
|
func rem(a, b int) (int, error) {
|
|
// TODO: Support other number types
|
|
if b == 0 {
|
|
return 0, ErrDivideByZero
|
|
}
|
|
return a % b, nil
|
|
}
|
|
|
|
//elvdoc:fn randint
|
|
//
|
|
// ```elvish
|
|
// randint $low $high
|
|
// ```
|
|
//
|
|
// Output a pseudo-random integer in the interval [$low, $high). Example:
|
|
//
|
|
// ```elvish-transcript
|
|
// ~> # Emulate dice
|
|
// randint 1 7
|
|
// ▶ 6
|
|
// ```
|
|
|
|
func randint(low, high int) (int, error) {
|
|
if low >= high {
|
|
return 0, ErrArgs
|
|
}
|
|
return low + rand.Intn(high-low), nil
|
|
}
|