github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/builtin_fn_misc.go (about)

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