Break builtin_fn.go into many files.

This commit is contained in:
Qi Xiao 2017-12-17 05:20:03 +00:00
parent ea2d5635ef
commit cbe45b815d
18 changed files with 1395 additions and 1271 deletions

File diff suppressed because it is too large Load Diff

82
eval/builtin_fn_cmd.go Normal file
View File

@ -0,0 +1,82 @@
package eval
import (
"errors"
"fmt"
"os"
"os/exec"
)
// Command and process control.
var ErrNotInSameGroup = errors.New("not in the same process group")
func init() {
addToBuiltinFns([]*BuiltinFn{
// Command resolution
{"resolve", resolveFn},
{"has-external", hasExternal},
{"search-external", searchExternal},
// Process control
{"fg", fg},
{"exec", execFn},
{"exit", exit},
})
}
func resolveFn(ec *EvalCtx, args []Value, opts map[string]Value) {
var cmd String
ScanArgs(args, &cmd)
TakeNoOpt(opts)
out := ec.ports[1].Chan
out <- resolve(string(cmd), ec)
}
func hasExternal(ec *EvalCtx, args []Value, opts map[string]Value) {
var cmd String
ScanArgs(args, &cmd)
TakeNoOpt(opts)
_, err := exec.LookPath(string(cmd))
ec.OutputChan() <- Bool(err == nil)
}
func searchExternal(ec *EvalCtx, args []Value, opts map[string]Value) {
var cmd String
ScanArgs(args, &cmd)
TakeNoOpt(opts)
path, err := exec.LookPath(string(cmd))
maybeThrow(err)
out := ec.ports[1].Chan
out <- String(path)
}
func exit(ec *EvalCtx, args []Value, opts map[string]Value) {
var codes []int
ScanArgsVariadic(args, &codes)
TakeNoOpt(opts)
doexit := func(i int) {
preExit(ec)
os.Exit(i)
}
switch len(codes) {
case 0:
doexit(0)
case 1:
doexit(codes[0])
default:
throw(ErrArgs)
}
}
func preExit(ec *EvalCtx) {
err := ec.Daemon.Close()
if err != nil {
fmt.Fprintln(os.Stderr, err)
}
}

View File

@ -0,0 +1,5 @@
package eval
func init() {
addToEvalTests([]evalTest{})
}

View File

@ -0,0 +1,250 @@
package eval
import (
"errors"
"fmt"
"io"
"strconv"
"github.com/elves/elvish/util"
)
// Sequence, list and maps.
func init() {
addToBuiltinFns([]*BuiltinFn{
{"range", rangeFn},
{"repeat", repeat},
{"explode", explode},
{"assoc", assoc},
{"dissoc", dissoc},
{"all", all},
{"take", take},
{"drop", drop},
{"has-key", hasKey},
{"has-value", hasValue},
{"count", count},
{"keys", keys},
})
}
func rangeFn(ec *EvalCtx, args []Value, opts map[string]Value) {
var step float64
ScanOpts(opts, OptToScan{"step", &step, String("1")})
var lower, upper float64
var err error
switch len(args) {
case 1:
upper, err = toFloat(args[0])
maybeThrow(err)
case 2:
lower, err = toFloat(args[0])
maybeThrow(err)
upper, err = toFloat(args[1])
maybeThrow(err)
default:
throw(ErrArgs)
}
out := ec.ports[1].Chan
for f := lower; f < upper; f += step {
out <- floatToString(f)
}
}
func repeat(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
n int
v Value
)
ScanArgs(args, &n, &v)
TakeNoOpt(opts)
out := ec.OutputChan()
for i := 0; i < n; i++ {
out <- v
}
}
// explode puts each element of the argument.
func explode(ec *EvalCtx, args []Value, opts map[string]Value) {
var v IterableValue
ScanArgs(args, &v)
TakeNoOpt(opts)
out := ec.ports[1].Chan
v.Iterate(func(e Value) bool {
out <- e
return true
})
}
func assoc(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
a Assocer
k, v Value
)
ScanArgs(args, &a, &k, &v)
TakeNoOpt(opts)
ec.OutputChan() <- a.Assoc(k, v)
}
func dissoc(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
a Dissocer
k Value
)
ScanArgs(args, &a, &k)
TakeNoOpt(opts)
ec.OutputChan() <- a.Dissoc(k)
}
const allBufferSize = 4096
func all(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
valuesDone := make(chan struct{})
go func() {
for input := range ec.ports[0].Chan {
ec.ports[1].Chan <- input
}
close(valuesDone)
}()
_, err := io.Copy(ec.ports[1].File, ec.ports[0].File)
<-valuesDone
if err != nil {
throwf("cannot copy byte input: %s", err)
}
}
func take(ec *EvalCtx, args []Value, opts map[string]Value) {
var n int
iterate := ScanArgsOptionalInput(ec, args, &n)
TakeNoOpt(opts)
out := ec.ports[1].Chan
i := 0
iterate(func(v Value) {
if i < n {
out <- v
}
i++
})
}
func drop(ec *EvalCtx, args []Value, opts map[string]Value) {
var n int
iterate := ScanArgsOptionalInput(ec, args, &n)
TakeNoOpt(opts)
out := ec.ports[1].Chan
i := 0
iterate(func(v Value) {
if i >= n {
out <- v
}
i++
})
}
func hasValue(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
var container, value Value
var found bool
ScanArgs(args, &container, &value)
switch container := container.(type) {
case Iterable:
container.Iterate(func(v Value) bool {
found = (v == value)
return !found
})
case MapLike:
container.IterateKey(func(v Value) bool {
found = (container.IndexOne(v) == value)
return !found
})
default:
throw(fmt.Errorf("argument of type '%s' is not iterable", container.Kind()))
}
ec.ports[1].Chan <- Bool(found)
}
func hasKey(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
var container, key Value
var found bool
ScanArgs(args, &container, &key)
switch container := container.(type) {
case HasKeyer:
found = container.HasKey(key)
case Lener:
// XXX(xiaq): Not all types that implement Lener have numerical indices
err := util.PCall(func() {
ParseAndFixListIndex(ToString(key), container.Len())
})
found = (err == nil)
default:
throw(fmt.Errorf("couldn't get key or index of type '%s'", container.Kind()))
}
ec.ports[1].Chan <- Bool(found)
}
func count(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
var n int
switch len(args) {
case 0:
// Count inputs.
ec.IterateInputs(func(Value) {
n++
})
case 1:
// Get length of argument.
v := args[0]
if lener, ok := v.(Lener); ok {
n = lener.Len()
} else if iterator, ok := v.(Iterable); ok {
iterator.Iterate(func(Value) bool {
n++
return true
})
} else {
throw(fmt.Errorf("cannot get length of a %s", v.Kind()))
}
default:
throw(errors.New("want 0 or 1 argument"))
}
ec.ports[1].Chan <- String(strconv.Itoa(n))
}
func keys(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
var iter IterateKeyer
ScanArgs(args, &iter)
out := ec.ports[1].Chan
iter.IterateKey(func(v Value) bool {
out <- v
return true
})
}

View File

@ -0,0 +1,44 @@
package eval
func init() {
addToEvalTests([]evalTest{
{`range 3`, want{out: strs("0", "1", "2")}},
{`range 1 3`, want{out: strs("1", "2")}},
{`range 0 10 &step=3`, want{out: strs("0", "3", "6", "9")}},
{`repeat 4 foo`, want{out: strs("foo", "foo", "foo", "foo")}},
{`explode [foo bar]`, want{out: strs("foo", "bar")}},
{`put (assoc [0] 0 zero)[0]`, want{out: strs("zero")}},
{`put (assoc [&] k v)[k]`, want{out: strs("v")}},
{`put (assoc [&k=v] k v2)[k]`, want{out: strs("v2")}},
{`has-key (dissoc [&k=v] k) k`, want{out: bools(false)}},
{`put foo bar | all`, want{out: strs("foo", "bar")}},
{`echo foobar | all`, want{bytesOut: []byte("foobar\n")}},
{`{ put foo bar; echo foobar } | all`,
want{out: strs("foo", "bar"), bytesOut: []byte("foobar\n")}},
{`range 100 | take 2`, want{out: strs("0", "1")}},
{`range 100 | drop 98`, want{out: strs("98", "99")}},
{`has-key [foo bar] 0`, want{out: bools(true)}},
{`has-key [foo bar] 0:1`, want{out: bools(true)}},
{`has-key [foo bar] 0:20`, want{out: bools(false)}},
{`has-key [&lorem=ipsum &foo=bar] lorem`, want{out: bools(true)}},
{`has-key [&lorem=ipsum &foo=bar] loremwsq`, want{out: bools(false)}},
{`has-value [&lorem=ipsum &foo=bar] lorem`, want{out: bools(false)}},
{`has-value [&lorem=ipsum &foo=bar] bar`, want{out: bools(true)}},
{`has-value [foo bar] bar`, want{out: bools(true)}},
{`has-value [foo bar] badehose`, want{out: bools(false)}},
{`has-value "foo" o`, want{out: bools(true)}},
{`has-value "foo" d`, want{out: bools(false)}},
{`range 100 | count`, want{out: strs("100")}},
{`count [(range 100)]`, want{out: strs("100")}},
{`keys [&]`, wantNothing},
{`keys [&a=foo]`, want{out: strs("a")}},
// Windows does not have an external sort command. Disabled until we have a
// builtin sort command.
// {`keys [&a=foo &b=bar] | each echo | sort | each put`, want{out: strs("a", "b")}},
})
}

154
eval/builtin_fn_flow.go Normal file
View File

@ -0,0 +1,154 @@
package eval
import (
"errors"
"sync"
)
// Flow control.
func init() {
addToBuiltinFns([]*BuiltinFn{
{"run-parallel", runParallel},
// Iterations.
{"each", each},
{"peach", peach},
// Exception and control
{"fail", fail},
{"multi-error", multiErrorFn},
{"return", returnFn},
{"break", breakFn},
{"continue", continueFn},
})
}
func runParallel(ec *EvalCtx, args []Value, opts map[string]Value) {
var functions []CallableValue
ScanArgsVariadic(args, &functions)
TakeNoOpt(opts)
var waitg sync.WaitGroup
waitg.Add(len(functions))
exceptions := make([]*Exception, len(functions))
for i, function := range functions {
go func(ec *EvalCtx, function CallableValue, exception **Exception) {
err := ec.PCall(function, NoArgs, NoOpts)
if err != nil {
*exception = err.(*Exception)
}
waitg.Done()
}(ec.fork("[run-parallel function]"), function, &exceptions[i])
}
waitg.Wait()
maybeThrow(ComposeExceptionsFromPipeline(exceptions))
}
// each takes a single closure and applies it to all input values.
func each(ec *EvalCtx, args []Value, opts map[string]Value) {
var f CallableValue
iterate := ScanArgsOptionalInput(ec, args, &f)
TakeNoOpt(opts)
broken := false
iterate(func(v Value) {
if broken {
return
}
// NOTE We don't have the position range of the closure in the source.
// Ideally, it should be kept in the Closure itself.
newec := ec.fork("closure of each")
newec.ports[0] = DevNullClosedChan
ex := newec.PCall(f, []Value{v}, NoOpts)
ClosePorts(newec.ports)
if ex != nil {
switch ex.(*Exception).Cause {
case nil, Continue:
// nop
case Break:
broken = true
default:
throw(ex)
}
}
})
}
// peach takes a single closure and applies it to all input values in parallel.
func peach(ec *EvalCtx, args []Value, opts map[string]Value) {
var f CallableValue
iterate := ScanArgsOptionalInput(ec, args, &f)
TakeNoOpt(opts)
var w sync.WaitGroup
broken := false
var err error
iterate(func(v Value) {
if broken || err != nil {
return
}
w.Add(1)
go func() {
// NOTE We don't have the position range of the closure in the source.
// Ideally, it should be kept in the Closure itself.
newec := ec.fork("closure of each")
newec.ports[0] = DevNullClosedChan
ex := newec.PCall(f, []Value{v}, NoOpts)
ClosePorts(newec.ports)
if ex != nil {
switch ex.(*Exception).Cause {
case nil, Continue:
// nop
case Break:
broken = true
default:
err = ex
}
}
w.Done()
}()
})
w.Wait()
maybeThrow(err)
}
func fail(ec *EvalCtx, args []Value, opts map[string]Value) {
var msg String
ScanArgs(args, &msg)
TakeNoOpt(opts)
throw(errors.New(string(msg)))
}
func multiErrorFn(ec *EvalCtx, args []Value, opts map[string]Value) {
var excs []*Exception
ScanArgsVariadic(args, &excs)
TakeNoOpt(opts)
throw(PipelineError{excs})
}
func returnFn(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
throw(Return)
}
func breakFn(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
throw(Break)
}
func continueFn(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
throw(Continue)
}

View File

@ -0,0 +1,20 @@
package eval
func init() {
addToEvalTests([]evalTest{
{`run-parallel { put lorem } { echo ipsum }`,
want{out: strs("lorem"), bytesOut: []byte("ipsum\n")}},
{`put 1 233 | each put`, want{out: strs("1", "233")}},
{`echo "1\n233" | each put`, want{out: strs("1", "233")}},
{`each put [1 233]`, want{out: strs("1", "233")}},
{`range 10 | each [x]{ if (== $x 4) { break }; put $x }`,
want{out: strs("0", "1", "2", "3")}},
{`range 10 | each [x]{ if (== $x 4) { fail haha }; put $x }`,
want{out: strs("0", "1", "2", "3"), err: errAny}},
// TODO: test peach
{`fail haha`, want{err: errAny}},
{`return`, want{err: Return}},
})
}

130
eval/builtin_fn_fs.go Normal file
View File

@ -0,0 +1,130 @@
package eval
import (
"errors"
"os"
"path/filepath"
"github.com/elves/elvish/store/storedefs"
"github.com/elves/elvish/util"
)
// Filesystem.
var ErrStoreNotConnected = errors.New("store not connected")
func init() {
addToBuiltinFns([]*BuiltinFn{
// Directory
{"cd", cd},
{"dir-history", dirs},
// Path
{"path-abs", WrapStringToStringError(filepath.Abs)},
{"path-base", WrapStringToString(filepath.Base)},
{"path-clean", WrapStringToString(filepath.Clean)},
{"path-dir", WrapStringToString(filepath.Dir)},
{"path-ext", WrapStringToString(filepath.Ext)},
{"eval-symlinks", WrapStringToStringError(filepath.EvalSymlinks)},
{"tilde-abbr", tildeAbbr},
// File types
{"-is-dir", isDir},
})
}
func WrapStringToString(f func(string) string) BuiltinFnImpl {
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
s := mustGetOneString(args)
ec.ports[1].Chan <- String(f(s))
}
}
func WrapStringToStringError(f func(string) (string, error)) BuiltinFnImpl {
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
s := mustGetOneString(args)
result, err := f(s)
maybeThrow(err)
ec.ports[1].Chan <- String(result)
}
}
var errMustBeOneString = errors.New("must be one string argument")
func mustGetOneString(args []Value) string {
if len(args) != 1 {
throw(errMustBeOneString)
}
s, ok := args[0].(String)
if !ok {
throw(errMustBeOneString)
}
return string(s)
}
func cd(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
var dir string
if len(args) == 0 {
dir = mustGetHome("")
} else if len(args) == 1 {
dir = ToString(args[0])
} else {
throw(ErrArgs)
}
cdInner(dir, ec)
}
func cdInner(dir string, ec *EvalCtx) {
maybeThrow(Chdir(dir, ec.Daemon))
}
var dirDescriptor = NewStructDescriptor("path", "score")
func dirs(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
if ec.Daemon == nil {
throw(ErrStoreNotConnected)
}
dirs, err := ec.Daemon.Dirs(storedefs.NoBlacklist)
if err != nil {
throw(errors.New("store error: " + err.Error()))
}
out := ec.ports[1].Chan
for _, dir := range dirs {
out <- &Struct{dirDescriptor, []Value{
String(dir.Path),
floatToString(dir.Score),
}}
}
}
func tildeAbbr(ec *EvalCtx, args []Value, opts map[string]Value) {
var pathv String
ScanArgs(args, &pathv)
path := string(pathv)
TakeNoOpt(opts)
out := ec.ports[1].Chan
out <- String(util.TildeAbbr(path))
}
func isDir(ec *EvalCtx, args []Value, opts map[string]Value) {
var pathv String
ScanArgs(args, &pathv)
path := string(pathv)
TakeNoOpt(opts)
ec.OutputChan() <- Bool(isDirInner(path))
}
func isDirInner(path string) bool {
fi, err := os.Stat(path)
return err == nil && fi.Mode().IsDir()
}

View File

@ -0,0 +1,7 @@
package eval
func init() {
addToEvalTests([]evalTest{
{`path-base a/b/c.png`, want{out: strs("c.png")}},
})
}

204
eval/builtin_fn_io.go Normal file
View File

@ -0,0 +1,204 @@
package eval
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
)
// Input and output.
func init() {
addToBuiltinFns([]*BuiltinFn{
// Value output
{"put", put},
// Bytes output
{"print", print},
{"echo", echo},
{"pprint", pprint},
{"repr", repr},
// Bytes to value
{"slurp", slurp},
{"from-lines", fromLines},
{"from-json", fromJSON},
// Value to bytes
{"to-lines", toLines},
{"to-json", toJSON},
// File and pipe
{"fopen", fopen},
{"fclose", fclose},
{"pipe", pipe},
{"prclose", prclose},
{"pwclose", pwclose},
})
}
func put(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
out := ec.ports[1].Chan
for _, a := range args {
out <- a
}
}
func print(ec *EvalCtx, args []Value, opts map[string]Value) {
var sepv String
ScanOpts(opts, OptToScan{"sep", &sepv, String(" ")})
out := ec.ports[1].File
sep := string(sepv)
for i, arg := range args {
if i > 0 {
out.WriteString(sep)
}
out.WriteString(ToString(arg))
}
}
func echo(ec *EvalCtx, args []Value, opts map[string]Value) {
print(ec, args, opts)
ec.ports[1].File.WriteString("\n")
}
func pprint(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
out := ec.ports[1].File
for _, arg := range args {
out.WriteString(arg.Repr(0))
out.WriteString("\n")
}
}
func repr(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
out := ec.ports[1].File
for i, arg := range args {
if i > 0 {
out.WriteString(" ")
}
out.WriteString(arg.Repr(NoPretty))
}
out.WriteString("\n")
}
func slurp(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
in := ec.ports[0].File
out := ec.ports[1].Chan
all, err := ioutil.ReadAll(in)
maybeThrow(err)
out <- String(string(all))
}
func fromLines(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
in := ec.ports[0].File
out := ec.ports[1].Chan
linesToChan(in, out)
}
// fromJSON parses a stream of JSON data into Value's.
func fromJSON(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
in := ec.ports[0].File
out := ec.ports[1].Chan
dec := json.NewDecoder(in)
var v interface{}
for {
err := dec.Decode(&v)
if err != nil {
if err == io.EOF {
return
}
throw(err)
}
out <- FromJSONInterface(v)
}
}
func toLines(ec *EvalCtx, args []Value, opts map[string]Value) {
iterate := ScanArgsOptionalInput(ec, args)
TakeNoOpt(opts)
out := ec.ports[1].File
iterate(func(v Value) {
fmt.Fprintln(out, ToString(v))
})
}
// toJSON converts a stream of Value's to JSON data.
func toJSON(ec *EvalCtx, args []Value, opts map[string]Value) {
iterate := ScanArgsOptionalInput(ec, args)
TakeNoOpt(opts)
out := ec.ports[1].File
enc := json.NewEncoder(out)
iterate(func(v Value) {
err := enc.Encode(v)
maybeThrow(err)
})
}
func fopen(ec *EvalCtx, args []Value, opts map[string]Value) {
var namev String
ScanArgs(args, &namev)
name := string(namev)
TakeNoOpt(opts)
// TODO support opening files for writing etc as well.
out := ec.ports[1].Chan
f, err := os.Open(name)
maybeThrow(err)
out <- File{f}
}
func fclose(ec *EvalCtx, args []Value, opts map[string]Value) {
var f File
ScanArgs(args, &f)
TakeNoOpt(opts)
maybeThrow(f.inner.Close())
}
func pipe(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
r, w, err := os.Pipe()
out := ec.ports[1].Chan
maybeThrow(err)
out <- Pipe{r, w}
}
func prclose(ec *EvalCtx, args []Value, opts map[string]Value) {
var p Pipe
ScanArgs(args, &p)
TakeNoOpt(opts)
maybeThrow(p.r.Close())
}
func pwclose(ec *EvalCtx, args []Value, opts map[string]Value) {
var p Pipe
ScanArgs(args, &p)
TakeNoOpt(opts)
maybeThrow(p.w.Close())
}

View File

@ -0,0 +1,30 @@
package eval
func init() {
addToEvalTests([]evalTest{
{`put foo bar`, want{out: strs("foo", "bar")}},
{`print [foo bar]`, want{bytesOut: []byte("[foo bar]")}},
{`echo [foo bar]`, want{bytesOut: []byte("[foo bar]\n")}},
{`pprint [foo bar]`, want{bytesOut: []byte("[\n foo\n bar\n]\n")}},
{`print "a\nb" | slurp`, want{out: strs("a\nb")}},
{`print "a\nb" | from-lines`, want{out: strs("a", "b")}},
{`print "a\nb\n" | from-lines`, want{out: strs("a", "b")}},
{`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`,
want{out: []Value{
ConvertToMap(map[Value]Value{
String("k"): String("v"),
String("a"): NewList(strs("1", "2")...)}),
String("foo"),
}}},
{`echo 'invalid' | from-json`, want{err: errAny}},
{`put "l\norem" ipsum | to-lines`,
want{bytesOut: []byte("l\norem\nipsum\n")}},
{`put [&k=v &a=[1 2]] foo | to-json`,
want{bytesOut: []byte(`{"a":["1","2"],"k":"v"}
"foo"
`)}},
})
}

170
eval/builtin_fn_num.go Normal file
View File

@ -0,0 +1,170 @@
package eval
import (
"math"
"math/rand"
"strconv"
)
// Numerical operations.
func init() {
addToBuiltinFns([]*BuiltinFn{
// Comparison
{"<",
wrapNumCompare(func(a, b float64) bool { return a < b })},
{"<=",
wrapNumCompare(func(a, b float64) bool { return a <= b })},
{"==",
wrapNumCompare(func(a, b float64) bool { return a == b })},
{"!=",
wrapNumCompare(func(a, b float64) bool { return a != b })},
{">",
wrapNumCompare(func(a, b float64) bool { return a > b })},
{">=",
wrapNumCompare(func(a, b float64) bool { return a >= b })},
// Arithmetics
{"+", plus},
{"-", minus},
{"*", times},
{"/", slash},
{"^", pow},
{"%", mod},
// Random
{"rand", randFn},
{"randint", randint},
})
}
func wrapNumCompare(cmp func(a, b float64) bool) BuiltinFnImpl {
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
floats := make([]float64, len(args))
for i, a := range args {
f, err := toFloat(a)
maybeThrow(err)
floats[i] = f
}
result := true
for i := 0; i < len(floats)-1; i++ {
if !cmp(floats[i], floats[i+1]) {
result = false
break
}
}
ec.OutputChan() <- Bool(result)
}
}
func plus(ec *EvalCtx, args []Value, opts map[string]Value) {
var nums []float64
ScanArgsVariadic(args, &nums)
TakeNoOpt(opts)
out := ec.ports[1].Chan
sum := 0.0
for _, f := range nums {
sum += f
}
out <- floatToString(sum)
}
func minus(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
sum float64
nums []float64
)
ScanArgsVariadic(args, &sum, &nums)
TakeNoOpt(opts)
out := ec.ports[1].Chan
if len(nums) == 0 {
// Unary -
sum = -sum
} else {
for _, f := range nums {
sum -= f
}
}
out <- floatToString(sum)
}
func times(ec *EvalCtx, args []Value, opts map[string]Value) {
var nums []float64
ScanArgsVariadic(args, &nums)
TakeNoOpt(opts)
out := ec.ports[1].Chan
prod := 1.0
for _, f := range nums {
prod *= f
}
out <- floatToString(prod)
}
func slash(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
if len(args) == 0 {
// cd /
cdInner("/", ec)
return
}
// Division
divide(ec, args, opts)
}
func divide(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
prod float64
nums []float64
)
ScanArgsVariadic(args, &prod, &nums)
TakeNoOpt(opts)
out := ec.ports[1].Chan
for _, f := range nums {
prod /= f
}
out <- floatToString(prod)
}
func pow(ec *EvalCtx, args []Value, opts map[string]Value) {
var b, p float64
ScanArgs(args, &b, &p)
TakeNoOpt(opts)
out := ec.ports[1].Chan
out <- floatToString(math.Pow(b, p))
}
func mod(ec *EvalCtx, args []Value, opts map[string]Value) {
var a, b int
ScanArgs(args, &a, &b)
TakeNoOpt(opts)
out := ec.ports[1].Chan
out <- String(strconv.Itoa(a % b))
}
func randFn(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoArg(args)
TakeNoOpt(opts)
out := ec.ports[1].Chan
out <- floatToString(rand.Float64())
}
func randint(ec *EvalCtx, args []Value, opts map[string]Value) {
var low, high int
ScanArgs(args, &low, &high)
TakeNoOpt(opts)
if low >= high {
throw(ErrArgs)
}
out := ec.ports[1].Chan
i := low + rand.Intn(high-low)
out <- String(strconv.Itoa(i))
}

View File

@ -0,0 +1,20 @@
package eval
func init() {
addToEvalTests([]evalTest{
{`== 1 1.0`, want{out: bools(true)}},
{`== 10 0xa`, want{out: bools(true)}},
{`== a a`, want{err: errAny}},
{`> 0x10 1`, want{out: bools(true)}},
// TODO test more edge cases
{"+ 233100 233", want{out: strs("233333")}},
{"- 233333 233100", want{out: strs("233")}},
{"- 233", want{out: strs("-233")}},
{"* 353 661", want{out: strs("233333")}},
{"/ 233333 353", want{out: strs("661")}},
{"/ 1 0", want{out: strs("+Inf")}},
{"^ 16 2", want{out: strs("256")}},
{"% 23 7", want{out: strs("2")}},
})
}

239
eval/builtin_fn_str.go Normal file
View File

@ -0,0 +1,239 @@
package eval
import (
"bytes"
"errors"
"regexp"
"strconv"
"strings"
"github.com/elves/elvish/util"
)
// String operations.
var ErrInput = errors.New("input error")
func init() {
addToBuiltinFns([]*BuiltinFn{
{"<s",
wrapStrCompare(func(a, b string) bool { return a < b })},
{"<=s",
wrapStrCompare(func(a, b string) bool { return a <= b })},
{"==s",
wrapStrCompare(func(a, b string) bool { return a == b })},
{"!=s",
wrapStrCompare(func(a, b string) bool { return a != b })},
{">s",
wrapStrCompare(func(a, b string) bool { return a > b })},
{">=s",
wrapStrCompare(func(a, b string) bool { return a >= b })},
{"to-string", toString},
{"joins", joins},
{"splits", splits},
{"replaces", replaces},
{"ord", ord},
{"base", base},
{"wcswidth", wcswidth},
{"-override-wcwidth", overrideWcwidth},
{"has-prefix", hasPrefix},
{"has-suffix", hasSuffix},
{"eawk", eawk},
})
}
func wrapStrCompare(cmp func(a, b string) bool) BuiltinFnImpl {
return func(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
for _, a := range args {
if _, ok := a.(String); !ok {
throw(ErrArgs)
}
}
result := true
for i := 0; i < len(args)-1; i++ {
if !cmp(string(args[i].(String)), string(args[i+1].(String))) {
result = false
break
}
}
ec.OutputChan() <- Bool(result)
}
}
// toString converts all arguments to strings.
func toString(ec *EvalCtx, args []Value, opts map[string]Value) {
TakeNoOpt(opts)
out := ec.OutputChan()
for _, a := range args {
out <- String(ToString(a))
}
}
// joins joins all input strings with a delimiter.
func joins(ec *EvalCtx, args []Value, opts map[string]Value) {
var sepv String
iterate := ScanArgsOptionalInput(ec, args, &sepv)
sep := string(sepv)
TakeNoOpt(opts)
var buf bytes.Buffer
iterate(func(v Value) {
if s, ok := v.(String); ok {
if buf.Len() > 0 {
buf.WriteString(sep)
}
buf.WriteString(string(s))
} else {
throwf("join wants string input, got %s", v.Kind())
}
})
out := ec.ports[1].Chan
out <- String(buf.String())
}
// splits splits an argument strings by a delimiter and writes all pieces.
func splits(ec *EvalCtx, args []Value, opts map[string]Value) {
var s, sep String
ScanArgs(args, &sep, &s)
TakeNoOpt(opts)
out := ec.ports[1].Chan
parts := strings.Split(string(s), string(sep))
for _, p := range parts {
out <- String(p)
}
}
func replaces(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
old, repl, s String
optMax int
)
ScanArgs(args, &old, &repl, &s)
ScanOpts(opts, OptToScan{"max", &optMax, String("-1")})
ec.ports[1].Chan <- String(strings.Replace(string(s), string(old), string(repl), optMax))
}
func ord(ec *EvalCtx, args []Value, opts map[string]Value) {
var s String
ScanArgs(args, &s)
TakeNoOpt(opts)
out := ec.ports[1].Chan
for _, r := range s {
out <- String("0x" + strconv.FormatInt(int64(r), 16))
}
}
// ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or
// greater than 36.
var ErrBadBase = errors.New("bad base")
func base(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
b int
nums []int
)
ScanArgsVariadic(args, &b, &nums)
TakeNoOpt(opts)
if b < 2 || b > 36 {
throw(ErrBadBase)
}
out := ec.ports[1].Chan
for _, num := range nums {
out <- String(strconv.FormatInt(int64(num), b))
}
}
func wcswidth(ec *EvalCtx, args []Value, opts map[string]Value) {
var s String
ScanArgs(args, &s)
TakeNoOpt(opts)
out := ec.ports[1].Chan
out <- String(strconv.Itoa(util.Wcswidth(string(s))))
}
func overrideWcwidth(ec *EvalCtx, args []Value, opts map[string]Value) {
var (
s String
w int
)
ScanArgs(args, &s, &w)
TakeNoOpt(opts)
r, err := toRune(s)
maybeThrow(err)
util.OverrideWcwidth(r, w)
}
func hasPrefix(ec *EvalCtx, args []Value, opts map[string]Value) {
var s, prefix String
ScanArgs(args, &s, &prefix)
TakeNoOpt(opts)
ec.OutputChan() <- Bool(strings.HasPrefix(string(s), string(prefix)))
}
func hasSuffix(ec *EvalCtx, args []Value, opts map[string]Value) {
var s, suffix String
ScanArgs(args, &s, &suffix)
TakeNoOpt(opts)
ec.OutputChan() <- Bool(strings.HasSuffix(string(s), string(suffix)))
}
var eawkWordSep = regexp.MustCompile("[ \t]+")
// eawk takes a function. For each line in the input stream, it calls the
// function with the line and the words in the line. The words are found by
// stripping the line and splitting the line by whitespaces. The function may
// call break and continue. Overall this provides a similar functionality to
// awk, hence the name.
func eawk(ec *EvalCtx, args []Value, opts map[string]Value) {
var f CallableValue
iterate := ScanArgsOptionalInput(ec, args, &f)
TakeNoOpt(opts)
broken := false
iterate(func(v Value) {
if broken {
return
}
line, ok := v.(String)
if !ok {
throw(ErrInput)
}
args := []Value{line}
for _, field := range eawkWordSep.Split(strings.Trim(string(line), " \t"), -1) {
args = append(args, String(field))
}
newec := ec.fork("fn of eawk")
// TODO: Close port 0 of newec.
ex := newec.PCall(f, args, NoOpts)
ClosePorts(newec.ports)
if ex != nil {
switch ex.(*Exception).Cause {
case nil, Continue:
// nop
case Break:
broken = true
default:
throw(ex)
}
}
})
}

View File

@ -0,0 +1,26 @@
package eval
func init() {
addToEvalTests([]evalTest{
{`==s haha haha`, want{out: bools(true)}},
{`==s 10 10.0`, want{out: bools(false)}},
{`<s a b`, want{out: bools(true)}},
{`<s 2 10`, want{out: bools(false)}},
{`joins : [/usr /bin /tmp]`, want{out: strs("/usr:/bin:/tmp")}},
{`splits : /usr:/bin:/tmp`, want{out: strs("/usr", "/bin", "/tmp")}},
{`replaces : / ":usr:bin:tmp"`, want{out: strs("/usr/bin/tmp")}},
{`replaces &max=2 : / :usr:bin:tmp`, want{out: strs("/usr/bin:tmp")}},
{`ord a`, want{out: strs("0x61")}},
{`base 16 42 233`, want{out: strs("2a", "e9")}},
{`wcswidth 你好`, want{out: strs("4")}},
{`has-prefix golang go`, want{out: bools(true)}},
{`has-prefix golang x`, want{out: bools(false)}},
{`has-suffix golang x`, want{out: bools(false)}},
{`echo " ax by cz \n11\t22 33" | eawk [@a]{ put $a[-1] }`,
want{out: strs("cz", "33")}},
})
}

View File

@ -7,126 +7,14 @@ var builtinFnTests = []evalTest{
{"kind-of bare 'str' [] [&] []{ }",
want{out: strs("string", "string", "list", "map", "fn")}},
{`put foo bar`, want{out: strs("foo", "bar")}},
{`explode [foo bar]`, want{out: strs("foo", "bar")}},
{`print [foo bar]`, want{bytesOut: []byte("[foo bar]")}},
{`echo [foo bar]`, want{bytesOut: []byte("[foo bar]\n")}},
{`pprint [foo bar]`, want{bytesOut: []byte("[\n foo\n bar\n]\n")}},
{`print "a\nb" | slurp`, want{out: strs("a\nb")}},
{`print "a\nb" | from-lines`, want{out: strs("a", "b")}},
{`print "a\nb\n" | from-lines`, want{out: strs("a", "b")}},
{`echo '{"k": "v", "a": [1, 2]}' '"foo"' | from-json`,
want{out: []Value{
ConvertToMap(map[Value]Value{
String("k"): String("v"),
String("a"): NewList(strs("1", "2")...)}),
String("foo"),
}}},
{`echo 'invalid' | from-json`, want{err: errAny}},
{`put "l\norem" ipsum | to-lines`,
want{bytesOut: []byte("l\norem\nipsum\n")}},
{`put [&k=v &a=[1 2]] foo | to-json`,
want{bytesOut: []byte(`{"a":["1","2"],"k":"v"}
"foo"
`)}},
{`joins : [/usr /bin /tmp]`, want{out: strs("/usr:/bin:/tmp")}},
{`splits : /usr:/bin:/tmp`, want{out: strs("/usr", "/bin", "/tmp")}},
{`replaces : / ":usr:bin:tmp"`, want{out: strs("/usr/bin/tmp")}},
{`replaces &max=2 : / :usr:bin:tmp`, want{out: strs("/usr/bin:tmp")}},
{`has-prefix golang go`, want{out: bools(true)}},
{`has-prefix golang x`, want{out: bools(false)}},
{`has-suffix golang x`, want{out: bools(false)}},
{`keys [&]`, wantNothing},
{`keys [&a=foo]`, want{out: strs("a")}},
// Windows does not have an external sort command. Disabled until we have a
// builtin sort command.
// {`keys [&a=foo &b=bar] | each echo | sort | each put`, want{out: strs("a", "b")}},
{`==s haha haha`, want{out: bools(true)}},
{`==s 10 10.0`, want{out: bools(false)}},
{`<s a b`, want{out: bools(true)}},
{`<s 2 10`, want{out: bools(false)}},
{`run-parallel { put lorem } { echo ipsum }`,
want{out: strs("lorem"), bytesOut: []byte("ipsum\n")}},
{`fail haha`, want{err: errAny}},
{`return`, want{err: Return}},
{`f=(constantly foo); $f; $f`, want{out: strs("foo", "foo")}},
{`(constantly foo) bad`, want{err: errAny}},
{`put 1 233 | each put`, want{out: strs("1", "233")}},
{`echo "1\n233" | each put`, want{out: strs("1", "233")}},
{`each put [1 233]`, want{out: strs("1", "233")}},
{`range 10 | each [x]{ if (== $x 4) { break }; put $x }`,
want{out: strs("0", "1", "2", "3")}},
{`range 10 | each [x]{ if (== $x 4) { fail haha }; put $x }`,
want{out: strs("0", "1", "2", "3"), err: errAny}},
{`repeat 4 foo`, want{out: strs("foo", "foo", "foo", "foo")}},
// TODO: test peach
{`range 3`, want{out: strs("0", "1", "2")}},
{`range 1 3`, want{out: strs("1", "2")}},
{`range 0 10 &step=3`, want{out: strs("0", "3", "6", "9")}},
{`put foo bar | all`, want{out: strs("foo", "bar")}},
{`echo foobar | all`, want{bytesOut: []byte("foobar\n")}},
{`{ put foo bar; echo foobar } | all`,
want{out: strs("foo", "bar"), bytesOut: []byte("foobar\n")}},
{`range 100 | take 2`, want{out: strs("0", "1")}},
{`range 100 | drop 98`, want{out: strs("98", "99")}},
{`range 100 | count`, want{out: strs("100")}},
{`count [(range 100)]`, want{out: strs("100")}},
{`echo " ax by cz \n11\t22 33" | eawk [@a]{ put $a[-1] }`,
want{out: strs("cz", "33")}},
{`path-base a/b/c.png`, want{out: strs("c.png")}},
// TODO test more edge cases
{"+ 233100 233", want{out: strs("233333")}},
{"- 233333 233100", want{out: strs("233")}},
{"- 233", want{out: strs("-233")}},
{"* 353 661", want{out: strs("233333")}},
{"/ 233333 353", want{out: strs("661")}},
{"/ 1 0", want{out: strs("+Inf")}},
{"^ 16 2", want{out: strs("256")}},
{"% 23 7", want{out: strs("2")}},
{`== 1 1.0`, want{out: bools(true)}},
{`== 10 0xa`, want{out: bools(true)}},
{`== a a`, want{err: errAny}},
{`> 0x10 1`, want{out: bools(true)}},
{`is 1 1`, want{out: bools(true)}},
{`is [] []`, want{out: bools(true)}},
{`is [1] [1]`, want{out: bools(false)}},
{`eq 1 1`, want{out: bools(true)}},
{`eq [] []`, want{out: bools(true)}},
{`ord a`, want{out: strs("0x61")}},
{`base 16 42 233`, want{out: strs("2a", "e9")}},
{`wcswidth 你好`, want{out: strs("4")}},
{`has-key [foo bar] 0`, want{out: bools(true)}},
{`has-key [foo bar] 0:1`, want{out: bools(true)}},
{`has-key [foo bar] 0:20`, want{out: bools(false)}},
{`has-key [&lorem=ipsum &foo=bar] lorem`, want{out: bools(true)}},
{`has-key [&lorem=ipsum &foo=bar] loremwsq`, want{out: bools(false)}},
{`has-value [&lorem=ipsum &foo=bar] lorem`, want{out: bools(false)}},
{`has-value [&lorem=ipsum &foo=bar] bar`, want{out: bools(true)}},
{`has-value [foo bar] bar`, want{out: bools(true)}},
{`has-value [foo bar] badehose`, want{out: bools(false)}},
{`has-value "foo" o`, want{out: bools(true)}},
{`has-value "foo" d`, want{out: bools(false)}},
{`put (assoc [0] 0 zero)[0]`, want{out: strs("zero")}},
{`put (assoc [&] k v)[k]`, want{out: strs("v")}},
{`put (assoc [&k=v] k v2)[k]`, want{out: strs("v2")}},
{`has-key (dissoc [&k=v] k) k`, want{out: bools(false)}},
{`f=(constantly foo); $f; $f`, want{out: strs("foo", "foo")}},
{`(constantly foo) bad`, want{err: errAny}},
}
func init() {