elvish/pkg/eval/builtin_fn_num.go
Qi Xiao a2790af67a pkg/eval: Clean up the structure and methods of Evaler and Frame.
- Make Evaler mostly thread-safe. The only remaining thread-unsafe part is the
  modules field, which is more tricky than other fields.

- Remove the state and evalerScopes type, and move their fields into Evaler.

- Expose valuePrefix via a get method, and change PortsFromFiles to take the
  prefix instead of a *Evaler. Also expose a PortsFromStdFiles.

- Make Evaler a normal field of Frame, instead of an embedded field. This makes
  access to global states more explicit.
2021-01-05 00:22:09 +00:00

292 lines
5.2 KiB
Go

package eval
import (
"math/rand"
"github.com/elves/elvish/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,
// Comparison
"<": lt,
"<=": le,
"==": eqNum,
"!=": ne,
">": gt,
">=": ge,
// Arithmetic
"+": plus,
"-": minus,
"*": times,
"/": slash,
"%": mod,
// Random
"rand": rand.Float64,
"randint": randint,
})
}
//elvdoc:fn float64
//
// ```elvish
// float64 123
// float64 NaN
// float64 Inf
// ```
//
// Explicitly convert a string to a `float64` data type.
//
// This command is seldom needed since commands that operate on numbers will
// convert a string to a number. See the discussion of the
// [number](language.html#number) data type.
func toFloat64(f float64) float64 {
return f
}
//elvdoc:fn &lt; &lt;= == != &gt; &gt;=
//
// ```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 ...float64) bool {
for i := 0; i < len(nums)-1; i++ {
if !(nums[i] < nums[i+1]) {
return false
}
}
return true
}
func le(nums ...float64) bool {
for i := 0; i < len(nums)-1; i++ {
if !(nums[i] <= nums[i+1]) {
return false
}
}
return true
}
func eqNum(nums ...float64) bool {
for i := 0; i < len(nums)-1; i++ {
if !(nums[i] == nums[i+1]) {
return false
}
}
return true
}
func ne(nums ...float64) bool {
for i := 0; i < len(nums)-1; i++ {
if !(nums[i] != nums[i+1]) {
return false
}
}
return true
}
func gt(nums ...float64) bool {
for i := 0; i < len(nums)-1; i++ {
if !(nums[i] > nums[i+1]) {
return false
}
}
return true
}
func ge(nums ...float64) bool {
for i := 0; i < len(nums)-1; i++ {
if !(nums[i] >= nums[i+1]) {
return false
}
}
return true
}
//elvdoc:fn + - * /
//
// ```elvish
// + $summand...
// - $minuend $subtrahend...
// * $factor...
// / $dividend $divisor...
// ```
//
// Basic arithmetic operations of adding, subtraction, multiplication and division
// respectively.
//
// All of them can take multiple arguments:
//
// ```elvish-transcript
// ~> + 2 5 7 # 2 + 5 + 7
// ▶ 14
// ~> - 2 5 7 # 2 - 5 - 7
// ▶ -10
// ~> * 2 5 7 # 2 * 5 * 7
// ▶ 70
// ~> / 2 5 7 # 2 / 5 / 7
// ▶ 0.05714285714285715
// ```
//
// When given one element, they all output their sole argument (given that it is a
// valid number). When given no argument,
//
// - `+` outputs 0, and `*` outputs 1. You can think that they both have a
// "hidden" argument of 0 or 1, which does not alter their behaviors (in
// mathematical terms, 0 and 1 are
// [identity elements](https://en.wikipedia.org/wiki/Identity_element) of
// addition and multiplication, respectively).
//
// - `-` throws an exception.
//
// - `/` becomes a synonym for `cd /`, due to the implicit cd feature. (The
// implicit cd feature will probably change to avoid this oddity).
func plus(nums ...float64) float64 {
sum := 0.0
for _, f := range nums {
sum += f
}
return sum
}
func minus(sum float64, nums ...float64) float64 {
if len(nums) == 0 {
// Unary -
return -sum
}
for _, f := range nums {
sum -= f
}
return sum
}
func times(nums ...float64) float64 {
prod := 1.0
for _, f := range nums {
prod *= f
}
return prod
}
func slash(fm *Frame, args ...float64) error {
if len(args) == 0 {
// cd /
return fm.Evaler.Chdir("/")
}
// Division
divide(fm, args[0], args[1:]...)
return nil
}
func divide(fm *Frame, prod float64, nums ...float64) {
out := fm.OutputChan()
for _, f := range nums {
prod /= f
}
out <- vals.FromGo(prod)
}
//elvdoc:fn %
//
// ```elvish
// % $dividend $divisor
// ```
//
// Output the remainder after dividing `$dividend` by `$divisor`. Both must be
// integers. Example:
//
// ```elvish-transcript
// ~> % 23 7
// ▶ 2
// ```
func mod(a, b int) (int, error) {
if b == 0 {
return 0, ErrArgs
}
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
}