2013-10-01 21:34:49 +08:00
|
|
|
package eval
|
|
|
|
|
2019-04-19 05:57:14 +08:00
|
|
|
// Misc builtin functions.
|
2014-04-02 10:51:09 +08:00
|
|
|
|
2013-10-02 22:10:07 +08:00
|
|
|
import (
|
2014-01-16 09:24:14 +08:00
|
|
|
"fmt"
|
2018-03-01 10:50:27 +08:00
|
|
|
"io/ioutil"
|
2016-03-17 22:28:00 +08:00
|
|
|
"math/rand"
|
2016-11-06 17:47:20 +08:00
|
|
|
"net"
|
2018-01-05 12:07:11 +08:00
|
|
|
"path/filepath"
|
2020-08-17 06:02:51 +08:00
|
|
|
"sync"
|
2016-02-10 01:18:18 +08:00
|
|
|
"time"
|
2018-03-01 10:50:27 +08:00
|
|
|
"unicode/utf8"
|
2016-02-14 04:05:35 +08:00
|
|
|
|
2019-12-24 04:00:59 +08:00
|
|
|
"github.com/elves/elvish/pkg/eval/vals"
|
|
|
|
"github.com/elves/elvish/pkg/parse"
|
2013-10-02 22:10:07 +08:00
|
|
|
)
|
|
|
|
|
2017-12-17 13:20:03 +08:00
|
|
|
// Builtins that have not been put into their own groups go here.
|
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
// TODO(xiaq): Document esleep.
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
addBuiltinFns(map[string]interface{}{
|
|
|
|
"nop": nop,
|
|
|
|
"kind-of": kindOf,
|
|
|
|
"constantly": constantly,
|
|
|
|
|
|
|
|
"resolve": resolve,
|
|
|
|
|
2020-08-17 06:02:51 +08:00
|
|
|
"eval": eval,
|
2020-08-17 01:46:29 +08:00
|
|
|
"-source": source,
|
|
|
|
|
|
|
|
// Time
|
|
|
|
"esleep": sleep,
|
|
|
|
"time": timeCmd,
|
|
|
|
|
|
|
|
"-ifaddrs": _ifaddrs,
|
|
|
|
})
|
|
|
|
|
|
|
|
// For rand and randint.
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:12:50 +08:00
|
|
|
//elvdoc:fn nop
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// nop &any-opt= $value...
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Accepts arbitrary arguments and options and does exactly nothing.
|
|
|
|
//
|
|
|
|
// Examples:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> nop
|
|
|
|
// ~> nop a b c
|
|
|
|
// ~> nop &k=v
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Etymology: Various languages, in particular NOP in
|
|
|
|
// [assembly languages](https://en.wikipedia.org/wiki/NOP).
|
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
func nop(opts RawOptions, args ...interface{}) {
|
|
|
|
// Do nothing
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:12:50 +08:00
|
|
|
//elvdoc:fn kind-of
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// kind-of $value...
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Output the kinds of `$value`s. Example:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> kind-of lorem [] [&]
|
|
|
|
// ▶ string
|
|
|
|
// ▶ list
|
|
|
|
// ▶ map
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// The terminology and definition of "kind" is subject to change.
|
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
func kindOf(fm *Frame, args ...interface{}) {
|
|
|
|
out := fm.ports[1].Chan
|
|
|
|
for _, a := range args {
|
|
|
|
out <- vals.Kind(a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:12:50 +08:00
|
|
|
//elvdoc:fn constantly
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// constantly $value...
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Output a function that takes no arguments and outputs `$value`s when called.
|
|
|
|
// Examples:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> f=(constantly lorem ipsum)
|
|
|
|
// ~> $f
|
|
|
|
// ▶ lorem
|
|
|
|
// ▶ ipsum
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// The above example is actually equivalent to simply `f = []{ put lorem ipsum }`;
|
|
|
|
// it is most useful when the argument is **not** a literal value, e.g.
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> f = (constantly (uname))
|
|
|
|
// ~> $f
|
|
|
|
// ▶ Darwin
|
|
|
|
// ~> $f
|
|
|
|
// ▶ Darwin
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// The above code only calls `uname` once, while if you do `f = []{ put (uname) }`,
|
|
|
|
// every time you invoke `$f`, `uname` will be called.
|
|
|
|
//
|
|
|
|
// Etymology: [Clojure](https://clojuredocs.org/clojure.core/constantly).
|
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
func constantly(args ...interface{}) Callable {
|
|
|
|
// TODO(xiaq): Repr of this function is not right.
|
|
|
|
return NewGoFn(
|
|
|
|
"created by constantly",
|
|
|
|
func(fm *Frame) {
|
|
|
|
out := fm.ports[1].Chan
|
|
|
|
for _, v := range args {
|
|
|
|
out <- v
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:12:50 +08:00
|
|
|
//elvdoc:fn resolve
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// resolve $command
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Resolve `$command`. Command resolution is described in the
|
|
|
|
// [language reference](language.html). (TODO: actually describe it there.)
|
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> resolve echo
|
|
|
|
// ▶ <builtin echo>
|
|
|
|
// ~> fn f { }
|
|
|
|
// ~> resolve f
|
|
|
|
// ▶ <closure 0xc4201c24d0>
|
|
|
|
// ~> resolve cat
|
|
|
|
// ▶ <external cat>
|
|
|
|
// ```
|
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
func resolve(fm *Frame, head string) string {
|
|
|
|
// Emulate static resolution of a command head. This needs to be kept in
|
|
|
|
// sync with (*compiler).form.
|
|
|
|
|
|
|
|
_, special := builtinSpecials[head]
|
|
|
|
if special {
|
|
|
|
return "special"
|
|
|
|
}
|
|
|
|
sigil, qname := SplitVariableRef(head)
|
|
|
|
if sigil == "" && fm.ResolveVar(qname+FnSuffix) != nil {
|
|
|
|
return "$" + qname + FnSuffix
|
|
|
|
}
|
|
|
|
return "(external " + parse.Quote(head) + ")"
|
|
|
|
}
|
|
|
|
|
2020-08-17 06:02:51 +08:00
|
|
|
//elvdoc:fn eval
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// eval $code &ns=$nil
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Evaluates `$code`, which should be a string. The evaluation happens in the
|
|
|
|
// namespace specified by the `&ns` option. If it is `$nil` (the default), a
|
|
|
|
// fresh empty namespace is created.
|
|
|
|
//
|
|
|
|
// If `$code` fails to parse or compile, the parse error or compilation error is
|
|
|
|
// raised as an exception.
|
|
|
|
//
|
|
|
|
// Examples:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> eval 'put x'
|
|
|
|
// ▶ x
|
|
|
|
// ~> ns = (ns [&x=initial])
|
|
|
|
// ~> eval 'put $x; x = altered; put $x' &ns=$ns
|
|
|
|
// ▶ initial
|
|
|
|
// ▶ altered
|
|
|
|
// ~> put $ns[x]
|
|
|
|
// ▶ altered
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// NOTE: Unlike the `eval` found in many other dynamic languages, `eval` cannot
|
|
|
|
// affect the current namespace:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> eval 'x = value'
|
|
|
|
// ~> put $x
|
|
|
|
// compilation error: variable $x not found
|
|
|
|
// [tty 4], line 1: put $x
|
|
|
|
// ```
|
|
|
|
|
|
|
|
type evalOpts struct{ Ns Ns }
|
|
|
|
|
|
|
|
func (*evalOpts) SetDefaultOptions() {}
|
|
|
|
|
|
|
|
func eval(fm *Frame, opts evalOpts, code string) error {
|
|
|
|
src := parse.Source{Name: fmt.Sprintf("[eval %d]", nextEvalCount()), Code: code}
|
|
|
|
tree, err := parse.ParseWithDeprecation(src, fm.ports[2].File)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
ns := opts.Ns
|
|
|
|
if ns == nil {
|
|
|
|
ns = make(Ns)
|
|
|
|
}
|
|
|
|
newFm := &Frame{
|
|
|
|
fm.Evaler, src, ns, make(Ns),
|
|
|
|
fm.intCh, fm.ports, fm.traceback, fm.background}
|
|
|
|
op, err := compile(newFm.Builtin.static(), ns.static(), tree, fm.ports[2].File)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return newFm.Eval(op)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Used to generate unique names for each source passed to eval.
|
|
|
|
var (
|
|
|
|
evalCount int
|
|
|
|
evalCountMutex sync.Mutex
|
|
|
|
)
|
|
|
|
|
|
|
|
func nextEvalCount() int {
|
|
|
|
evalCountMutex.Lock()
|
|
|
|
defer evalCountMutex.Unlock()
|
|
|
|
evalCount++
|
|
|
|
return evalCount
|
|
|
|
}
|
|
|
|
|
2020-01-18 21:12:50 +08:00
|
|
|
//elvdoc:fn -source
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// -source $filename
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Read the named file, and evaluate it in the current scope.
|
|
|
|
//
|
|
|
|
// Examples:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> cat x.elv
|
|
|
|
// echo 'executing x.elv'
|
|
|
|
// foo = bar
|
|
|
|
// ~> -source x.elv
|
|
|
|
// executing x.elv
|
|
|
|
// ~> echo $foo
|
|
|
|
// bar
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Note that while in the example, you can reference `$foo` after sourcing `x.elv`,
|
|
|
|
// putting the `-source` command and reference to `$foo` in the **same code chunk**
|
|
|
|
// (e.g. by using <span class="key">Alt-Enter</span> to insert a literal Enter, or
|
|
|
|
// using `;`) is invalid:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> # A new Elvish session
|
|
|
|
// ~> cat x.elv
|
|
|
|
// echo 'executing x.elv'
|
|
|
|
// foo = bar
|
|
|
|
// ~> -source x.elv; echo $foo
|
|
|
|
// Compilation error: variable $foo not found
|
|
|
|
// [interactive], line 1:
|
|
|
|
// -source x.elv; echo $foo
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// This is because the reading of the file is done in the evaluation phase, while
|
|
|
|
// the check for variables happens at the compilation phase (before evaluation). So
|
|
|
|
// the compiler has no evidence showing that `$foo` is actually valid, and will
|
|
|
|
// complain. (See [here](../learn/unique-semantics.html#execution-phases) for a
|
|
|
|
// more detailed description of execution phases.)
|
|
|
|
//
|
|
|
|
// To work around this, you can add a forward declaration for `$foo`:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> # Another new session
|
|
|
|
// ~> cat x.elv
|
|
|
|
// echo 'executing x.elv'
|
|
|
|
// foo = bar
|
|
|
|
// ~> foo = ''; -source x.elv; echo $foo
|
|
|
|
// executing x.elv
|
|
|
|
// bar
|
|
|
|
// ```
|
|
|
|
|
2018-02-05 15:35:49 +08:00
|
|
|
func source(fm *Frame, fname string) error {
|
2018-03-01 10:50:27 +08:00
|
|
|
path, err := filepath.Abs(fname)
|
2018-02-05 15:35:49 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-01 10:50:27 +08:00
|
|
|
code, err := readFileUTF8(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-04-26 02:22:38 +08:00
|
|
|
src := parse.Source{Name: fname, Code: code, IsFile: true}
|
2020-07-15 07:04:03 +08:00
|
|
|
tree, err := parse.ParseWithDeprecation(src, fm.ports[2].File)
|
2018-03-01 10:50:27 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-03-03 12:21:59 +08:00
|
|
|
scriptGlobal := fm.local.static()
|
|
|
|
for name := range fm.up.static() {
|
|
|
|
scriptGlobal.set(name)
|
|
|
|
}
|
2020-04-26 20:14:51 +08:00
|
|
|
op, err := compile(fm.Builtin.static(), scriptGlobal, tree, fm.ports[2].File)
|
2018-03-01 10:50:27 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-10-12 19:57:41 +08:00
|
|
|
return fm.Eval(op)
|
2018-03-01 10:50:27 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
func readFileUTF8(fname string) (string, error) {
|
|
|
|
bytes, err := ioutil.ReadFile(fname)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
if !utf8.Valid(bytes) {
|
|
|
|
return "", fmt.Errorf("%s: source is not valid UTF-8", fname)
|
|
|
|
}
|
|
|
|
return string(bytes), nil
|
2017-04-11 08:16:43 +08:00
|
|
|
}
|
|
|
|
|
2018-02-05 15:35:49 +08:00
|
|
|
func sleep(fm *Frame, t float64) error {
|
2016-02-23 00:39:03 +08:00
|
|
|
d := time.Duration(float64(time.Second) * t)
|
|
|
|
select {
|
2018-02-05 15:35:49 +08:00
|
|
|
case <-fm.Interrupts():
|
|
|
|
return ErrInterrupted
|
2016-02-23 00:39:03 +08:00
|
|
|
case <-time.After(d):
|
2018-02-05 15:35:49 +08:00
|
|
|
return nil
|
2016-02-23 00:39:03 +08:00
|
|
|
}
|
2016-02-10 01:18:18 +08:00
|
|
|
}
|
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
//elvdoc:fn time
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// time &on-end=$nil $callable
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Runs the callable, and call `$on-end` with the duration it took, as a
|
|
|
|
// number in seconds. If `$on-end` is `$nil` (the default), prints the
|
|
|
|
// duration in human-readable form.
|
|
|
|
//
|
|
|
|
// If `$callable` throws an exception, the exception is propagated after the
|
|
|
|
// on-end or default printing is done.
|
|
|
|
//
|
|
|
|
// If `$on-end` throws an exception, it is propagated, unless `$callable` has
|
|
|
|
// already thrown an exception.
|
|
|
|
//
|
|
|
|
// Example:
|
|
|
|
//
|
|
|
|
// ```elvish-transcript
|
|
|
|
// ~> time { sleep 1 }
|
|
|
|
// 1.006060647s
|
|
|
|
// ~> time { sleep 0.01 }
|
|
|
|
// 1.288977ms
|
|
|
|
// ~> t = ''
|
|
|
|
// ~> time &on-end=[x]{ t = $x } { sleep 1 }
|
|
|
|
// ~> put $t
|
|
|
|
// ▶ (float64 1.000925004)
|
|
|
|
// ~> time &on-end=[x]{ t = $x } { sleep 0.01 }
|
|
|
|
// ~> put $t
|
|
|
|
// ▶ (float64 0.011030208)
|
|
|
|
// ```
|
|
|
|
|
2020-04-27 04:01:56 +08:00
|
|
|
type timeOpt struct{ OnEnd Callable }
|
|
|
|
|
|
|
|
func (o *timeOpt) SetDefaultOptions() {}
|
|
|
|
|
|
|
|
func timeCmd(fm *Frame, opts timeOpt, f Callable) error {
|
2017-04-11 08:16:43 +08:00
|
|
|
t0 := time.Now()
|
2018-03-01 10:17:56 +08:00
|
|
|
err := f.Call(fm, NoArgs, NoOpts)
|
2017-04-11 08:16:43 +08:00
|
|
|
t1 := time.Now()
|
|
|
|
|
|
|
|
dt := t1.Sub(t0)
|
2020-04-27 04:01:56 +08:00
|
|
|
if opts.OnEnd != nil {
|
|
|
|
newFm := fm.fork("on-end callback of time")
|
|
|
|
errCb := opts.OnEnd.Call(newFm, []interface{}{dt.Seconds()}, NoOpts)
|
|
|
|
if err == nil {
|
|
|
|
err = errCb
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fmt.Fprintln(fm.ports[1].File, dt)
|
|
|
|
}
|
2017-04-11 08:16:43 +08:00
|
|
|
|
2018-02-05 15:35:49 +08:00
|
|
|
return err
|
|
|
|
}
|
2016-11-06 17:47:20 +08:00
|
|
|
|
2020-08-17 01:46:29 +08:00
|
|
|
//elvdoc:fn -ifaddrs
|
|
|
|
//
|
|
|
|
// ```elvish
|
|
|
|
// -ifaddrs
|
|
|
|
// ```
|
|
|
|
//
|
|
|
|
// Output all IP addresses of the current host.
|
|
|
|
//
|
|
|
|
// This should be part of a networking module instead of the builtin module.
|
|
|
|
|
2018-02-05 15:35:49 +08:00
|
|
|
func _ifaddrs(fm *Frame) error {
|
2016-11-06 17:47:20 +08:00
|
|
|
addrs, err := net.InterfaceAddrs()
|
2018-02-05 15:35:49 +08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
out := fm.ports[1].Chan
|
2016-11-06 17:47:20 +08:00
|
|
|
for _, addr := range addrs {
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- addr.String()
|
2016-11-06 17:47:20 +08:00
|
|
|
}
|
2018-02-05 15:35:49 +08:00
|
|
|
return nil
|
2016-11-06 17:47:20 +08:00
|
|
|
}
|