github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_fn_misc.go (about)

     1  package eval
     2  
     3  // Misc builtin functions.
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"math/rand"
    10  	"net"
    11  	"sync"
    12  	"time"
    13  	"unicode/utf8"
    14  
    15  	"src.elv.sh/pkg/diag"
    16  	"src.elv.sh/pkg/eval/vals"
    17  	"src.elv.sh/pkg/parse"
    18  )
    19  
    20  var (
    21  	ErrNegativeSleepDuration = errors.New("sleep duration must be >= zero")
    22  	ErrInvalidSleepDuration  = errors.New("invalid sleep duration")
    23  )
    24  
    25  // Builtins that have not been put into their own groups go here.
    26  
    27  func init() {
    28  	addBuiltinFns(map[string]interface{}{
    29  		"nop":        nop,
    30  		"kind-of":    kindOf,
    31  		"constantly": constantly,
    32  
    33  		"resolve": resolve,
    34  
    35  		"eval":    eval,
    36  		"use-mod": useMod,
    37  
    38  		"deprecate": deprecate,
    39  
    40  		// Time
    41  		"sleep": sleep,
    42  		"time":  timeCmd,
    43  
    44  		"-ifaddrs": _ifaddrs,
    45  	})
    46  
    47  	// For rand and randint.
    48  	rand.Seed(time.Now().UTC().UnixNano())
    49  }
    50  
    51  //elvdoc:fn nop
    52  //
    53  // ```elvish
    54  // nop &any-opt= $value...
    55  // ```
    56  //
    57  // Accepts arbitrary arguments and options and does exactly nothing.
    58  //
    59  // Examples:
    60  //
    61  // ```elvish-transcript
    62  // ~> nop
    63  // ~> nop a b c
    64  // ~> nop &k=v
    65  // ```
    66  //
    67  // Etymology: Various languages, in particular NOP in
    68  // [assembly languages](https://en.wikipedia.org/wiki/NOP).
    69  
    70  func nop(opts RawOptions, args ...interface{}) {
    71  	// Do nothing
    72  }
    73  
    74  //elvdoc:fn kind-of
    75  //
    76  // ```elvish
    77  // kind-of $value...
    78  // ```
    79  //
    80  // Output the kinds of `$value`s. Example:
    81  //
    82  // ```elvish-transcript
    83  // ~> kind-of lorem [] [&]
    84  // ▶ string
    85  // ▶ list
    86  // ▶ map
    87  // ```
    88  //
    89  // The terminology and definition of "kind" is subject to change.
    90  
    91  func kindOf(fm *Frame, args ...interface{}) {
    92  	out := fm.OutputChan()
    93  	for _, a := range args {
    94  		out <- vals.Kind(a)
    95  	}
    96  }
    97  
    98  //elvdoc:fn constantly
    99  //
   100  // ```elvish
   101  // constantly $value...
   102  // ```
   103  //
   104  // Output a function that takes no arguments and outputs `$value`s when called.
   105  // Examples:
   106  //
   107  // ```elvish-transcript
   108  // ~> f=(constantly lorem ipsum)
   109  // ~> $f
   110  // ▶ lorem
   111  // ▶ ipsum
   112  // ```
   113  //
   114  // The above example is actually equivalent to simply `f = []{ put lorem ipsum }`;
   115  // it is most useful when the argument is **not** a literal value, e.g.
   116  //
   117  // ```elvish-transcript
   118  // ~> f = (constantly (uname))
   119  // ~> $f
   120  // ▶ Darwin
   121  // ~> $f
   122  // ▶ Darwin
   123  // ```
   124  //
   125  // The above code only calls `uname` once, while if you do `f = []{ put (uname) }`,
   126  // every time you invoke `$f`, `uname` will be called.
   127  //
   128  // Etymology: [Clojure](https://clojuredocs.org/clojure.core/constantly).
   129  
   130  func constantly(args ...interface{}) Callable {
   131  	// TODO(xiaq): Repr of this function is not right.
   132  	return NewGoFn(
   133  		"created by constantly",
   134  		func(fm *Frame) {
   135  			out := fm.OutputChan()
   136  			for _, v := range args {
   137  				out <- v
   138  			}
   139  		},
   140  	)
   141  }
   142  
   143  //elvdoc:fn resolve
   144  //
   145  // ```elvish
   146  // resolve $command
   147  // ```
   148  //
   149  // Output what `$command` resolves to in symbolic form. Command resolution is
   150  // described in the [language reference](language.html#ordinary-command).
   151  //
   152  // Example:
   153  //
   154  // ```elvish-transcript
   155  // ~> resolve echo
   156  // ▶ <builtin echo>
   157  // ~> fn f { }
   158  // ~> resolve f
   159  // ▶ <closure 0xc4201c24d0>
   160  // ~> resolve cat
   161  // ▶ <external cat>
   162  // ```
   163  
   164  func resolve(fm *Frame, head string) string {
   165  	special, fnRef := resolveCmdHeadInternally(fm, head, nil)
   166  	switch {
   167  	case special != nil:
   168  		return "special"
   169  	case fnRef != nil:
   170  		return "$" + head + FnSuffix
   171  	default:
   172  		return "(external " + parse.Quote(head) + ")"
   173  	}
   174  }
   175  
   176  //elvdoc:fn eval
   177  //
   178  // ```elvish
   179  // eval $code &ns=$nil &on-end=$nil
   180  // ```
   181  //
   182  // Evaluates `$code`, which should be a string. The evaluation happens in a
   183  // new, restricted namespace, whose initial set of variables can be specified by
   184  // the `&ns` option. After evaluation completes, the new namespace is passed to
   185  // the callback specified by `&on-end` if it is not nil.
   186  //
   187  // The namespace specified by `&ns` is never modified; it will not be affected
   188  // by the creation or deletion of variables by `$code`. However, the values of
   189  // the variables may be mutated by `$code`.
   190  //
   191  // If the `&ns` option is `$nil` (the default), a temporary namespace built by
   192  // amalgamating the local and upvalue scopes of the caller is used.
   193  //
   194  // If `$code` fails to parse or compile, the parse error or compilation error is
   195  // raised as an exception.
   196  //
   197  // Basic examples that do not modify the namespace or any variable:
   198  //
   199  // ```elvish-transcript
   200  // ~> eval 'put x'
   201  // ▶ x
   202  // ~> x = foo
   203  // ~> eval 'put $x'
   204  // ▶ foo
   205  // ~> ns = (ns [&x=bar])
   206  // ~> eval &ns=$ns 'put $x'
   207  // ▶ bar
   208  // ```
   209  //
   210  // Examples that modify existing variables:
   211  //
   212  // ```elvish-transcript
   213  // ~> y = foo
   214  // ~> eval 'y = bar'
   215  // ~> put $y
   216  // ▶ bar
   217  // ```
   218  //
   219  // Examples that creates new variables and uses the callback to access it:
   220  //
   221  // ```elvish-transcript
   222  // ~> eval 'z = lorem'
   223  // ~> put $z
   224  // compilation error: variable $z not found
   225  // [ttz 2], line 1: put $z
   226  // ~> saved-ns = $nil
   227  // ~> eval &on-end=[ns]{ saved-ns = $ns } 'z = lorem'
   228  // ~> put $saved-ns[z]
   229  // ▶ lorem
   230  // ```
   231  
   232  type evalOpts struct {
   233  	Ns    *Ns
   234  	OnEnd Callable
   235  }
   236  
   237  func (*evalOpts) SetDefaultOptions() {}
   238  
   239  func eval(fm *Frame, opts evalOpts, code string) error {
   240  	src := parse.Source{Name: fmt.Sprintf("[eval %d]", nextEvalCount()), Code: code}
   241  	ns := opts.Ns
   242  	if ns == nil {
   243  		ns = CombineNs(fm.up, fm.local)
   244  	}
   245  	// The stacktrace already contains the line that calls "eval", so we pass
   246  	// nil as the second argument.
   247  	newNs, exc := fm.Eval(src, nil, ns)
   248  	if opts.OnEnd != nil {
   249  		newFm := fm.fork("on-end callback of eval")
   250  		errCb := opts.OnEnd.Call(newFm, []interface{}{newNs}, NoOpts)
   251  		if exc == nil {
   252  			return errCb
   253  		}
   254  	}
   255  	return exc
   256  }
   257  
   258  // Used to generate unique names for each source passed to eval.
   259  var (
   260  	evalCount      int
   261  	evalCountMutex sync.Mutex
   262  )
   263  
   264  func nextEvalCount() int {
   265  	evalCountMutex.Lock()
   266  	defer evalCountMutex.Unlock()
   267  	evalCount++
   268  	return evalCount
   269  }
   270  
   271  //elvdoc:fn use-mod
   272  //
   273  // ```elvish
   274  // use-mod $use-spec
   275  // ```
   276  //
   277  // Imports a module, and outputs the namespace for the module.
   278  //
   279  // Most code should use the [use](language.html#importing-modules-with-use)
   280  // special command instead.
   281  //
   282  // Examples:
   283  //
   284  // ```elvish-transcript
   285  // ~> echo 'x = value' > a.elv
   286  // ~> put (use-mod ./a)[x]
   287  // ▶ value
   288  // ```
   289  
   290  func useMod(fm *Frame, spec string) (*Ns, error) {
   291  	return use(fm, spec, nil)
   292  }
   293  
   294  func readFileUTF8(fname string) (string, error) {
   295  	bytes, err := ioutil.ReadFile(fname)
   296  	if err != nil {
   297  		return "", err
   298  	}
   299  	if !utf8.Valid(bytes) {
   300  		return "", fmt.Errorf("%s: source is not valid UTF-8", fname)
   301  	}
   302  	return string(bytes), nil
   303  }
   304  
   305  //elvdoc:fn deprecate
   306  //
   307  // ```elvish
   308  // deprecate $msg
   309  // ```
   310  //
   311  // Shows the given deprecation message to stderr. If called from a function
   312  // or module, also shows the call site of the function or import site of the
   313  // module. Does nothing if the combination of the call site and the message has
   314  // been shown before.
   315  //
   316  // ```elvish-transcript
   317  // ~> deprecate msg
   318  // deprecation: msg
   319  // ~> fn f { deprecate msg }
   320  // ~> f
   321  // deprecation: msg
   322  // [tty 19], line 1: f
   323  // ~> exec
   324  // ~> deprecate msg
   325  // deprecation: msg
   326  // ~> fn f { deprecate msg }
   327  // ~> f
   328  // deprecation: msg
   329  // [tty 3], line 1: f
   330  // ~> f # a different call site; shows deprecate message
   331  // deprecation: msg
   332  // [tty 4], line 1: f
   333  // ~> fn g { f }
   334  // ~> g
   335  // deprecation: msg
   336  // [tty 5], line 1: fn g { f }
   337  // ~> g # same call site, no more deprecation message
   338  // ```
   339  
   340  func deprecate(fm *Frame, msg string) {
   341  	var ctx *diag.Context
   342  	if fm.traceback.Next != nil {
   343  		ctx = fm.traceback.Next.Head
   344  	}
   345  	fm.Deprecate(msg, ctx, 0)
   346  }
   347  
   348  // TimeAfter is used by the sleep command to obtain a channel that is delivered
   349  // a value after the specified time.
   350  //
   351  // It is a variable to allow for unit tests to efficiently test the behavior of
   352  // the `sleep` command, both by eliminating an actual sleep and verifying the
   353  // duration was properly parsed.
   354  var TimeAfter = func(fm *Frame, d time.Duration) <-chan time.Time {
   355  	return time.After(d)
   356  }
   357  
   358  //elvdoc:fn sleep
   359  //
   360  // ```elvish
   361  // sleep $duration
   362  // ```
   363  //
   364  // Pauses for at least the specified duration. The actual pause duration depends
   365  // on the system.
   366  //
   367  // This only affects the current Elvish context. It does not affect any other
   368  // contexts that might be executing in parallel as a consequence of a command
   369  // such as [`peach`](#peach).
   370  //
   371  // A duration can be a simple [number](../language.html#number) (with optional
   372  // fractional value) without an explicit unit suffix, with an implicit unit of
   373  // seconds.
   374  //
   375  // A duration can also be a string written as a sequence of decimal numbers,
   376  // each with optional fraction, plus a unit suffix. For example, "300ms",
   377  // "1.5h" or "1h45m7s". Valid time units are "ns", "us" (or "µs"), "ms", "s",
   378  // "m", "h".
   379  //
   380  // Passing a negative duration causes an exception; this is different from the
   381  // typical BSD or GNU `sleep` command that silently exits with a success status
   382  // without pausing when given a negative duration.
   383  //
   384  // See the [Go documentation](https://golang.org/pkg/time/#ParseDuration) for
   385  // more information about how durations are parsed.
   386  //
   387  // Examples:
   388  //
   389  // ```elvish-transcript
   390  // ~> sleep 0.1    # sleeps 0.1 seconds
   391  // ~> sleep 100ms  # sleeps 0.1 seconds
   392  // ~> sleep 1.5m   # sleeps 1.5 minutes
   393  // ~> sleep 1m30s  # sleeps 1.5 minutes
   394  // ~> sleep -1
   395  // Exception: sleep duration must be >= zero
   396  // [tty 8], line 1: sleep -1
   397  // ```
   398  
   399  func sleep(fm *Frame, duration interface{}) error {
   400  	var f float64
   401  	var d time.Duration
   402  
   403  	if err := vals.ScanToGo(duration, &f); err == nil {
   404  		d = time.Duration(f * float64(time.Second))
   405  	} else {
   406  		// See if it is a duration string rather than a simple number.
   407  		switch duration := duration.(type) {
   408  		case string:
   409  			d, err = time.ParseDuration(duration)
   410  			if err != nil {
   411  				return ErrInvalidSleepDuration
   412  			}
   413  		default:
   414  			return ErrInvalidSleepDuration
   415  		}
   416  	}
   417  
   418  	if d < 0 {
   419  		return ErrNegativeSleepDuration
   420  	}
   421  
   422  	select {
   423  	case <-fm.Interrupts():
   424  		return ErrInterrupted
   425  	case <-TimeAfter(fm, d):
   426  		return nil
   427  	}
   428  }
   429  
   430  //elvdoc:fn time
   431  //
   432  // ```elvish
   433  // time &on-end=$nil $callable
   434  // ```
   435  //
   436  // Runs the callable, and call `$on-end` with the duration it took, as a
   437  // number in seconds. If `$on-end` is `$nil` (the default), prints the
   438  // duration in human-readable form.
   439  //
   440  // If `$callable` throws an exception, the exception is propagated after the
   441  // on-end or default printing is done.
   442  //
   443  // If `$on-end` throws an exception, it is propagated, unless `$callable` has
   444  // already thrown an exception.
   445  //
   446  // Example:
   447  //
   448  // ```elvish-transcript
   449  // ~> time { sleep 1 }
   450  // 1.006060647s
   451  // ~> time { sleep 0.01 }
   452  // 1.288977ms
   453  // ~> t = ''
   454  // ~> time &on-end=[x]{ t = $x } { sleep 1 }
   455  // ~> put $t
   456  // ▶ (float64 1.000925004)
   457  // ~> time &on-end=[x]{ t = $x } { sleep 0.01 }
   458  // ~> put $t
   459  // ▶ (float64 0.011030208)
   460  // ```
   461  
   462  type timeOpt struct{ OnEnd Callable }
   463  
   464  func (o *timeOpt) SetDefaultOptions() {}
   465  
   466  func timeCmd(fm *Frame, opts timeOpt, f Callable) error {
   467  	t0 := time.Now()
   468  	err := f.Call(fm, NoArgs, NoOpts)
   469  	t1 := time.Now()
   470  
   471  	dt := t1.Sub(t0)
   472  	if opts.OnEnd != nil {
   473  		newFm := fm.fork("on-end callback of time")
   474  		errCb := opts.OnEnd.Call(newFm, []interface{}{dt.Seconds()}, NoOpts)
   475  		if err == nil {
   476  			err = errCb
   477  		}
   478  	} else {
   479  		fmt.Fprintln(fm.OutputFile(), dt)
   480  	}
   481  
   482  	return err
   483  }
   484  
   485  //elvdoc:fn -ifaddrs
   486  //
   487  // ```elvish
   488  // -ifaddrs
   489  // ```
   490  //
   491  // Output all IP addresses of the current host.
   492  //
   493  // This should be part of a networking module instead of the builtin module.
   494  
   495  func _ifaddrs(fm *Frame) error {
   496  	addrs, err := net.InterfaceAddrs()
   497  	if err != nil {
   498  		return err
   499  	}
   500  	out := fm.OutputChan()
   501  	for _, addr := range addrs {
   502  		out <- addr.String()
   503  	}
   504  	return nil
   505  }