2017-12-17 13:20:03 +08:00
|
|
|
package eval
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
"github.com/elves/elvish/eval/types"
|
2017-12-17 13:20:03 +08:00
|
|
|
"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 {
|
2018-01-01 04:31:45 +08:00
|
|
|
return func(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2017-12-17 13:20:03 +08:00
|
|
|
TakeNoOpt(opts)
|
|
|
|
for _, a := range args {
|
2018-01-25 09:40:15 +08:00
|
|
|
if _, ok := a.(string); !ok {
|
2017-12-17 13:20:03 +08:00
|
|
|
throw(ErrArgs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result := true
|
|
|
|
for i := 0; i < len(args)-1; i++ {
|
2018-01-25 09:40:15 +08:00
|
|
|
if !cmp(args[i].(string), args[i+1].(string)) {
|
2017-12-17 13:20:03 +08:00
|
|
|
result = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2018-01-01 23:21:15 +08:00
|
|
|
ec.OutputChan() <- types.Bool(result)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// toString converts all arguments to strings.
|
2018-01-01 04:31:45 +08:00
|
|
|
func toString(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2017-12-17 13:20:03 +08:00
|
|
|
TakeNoOpt(opts)
|
|
|
|
out := ec.OutputChan()
|
|
|
|
for _, a := range args {
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- types.ToString(a)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// joins joins all input strings with a delimiter.
|
2018-01-01 04:31:45 +08:00
|
|
|
func joins(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2018-01-25 09:40:15 +08:00
|
|
|
var sepv string
|
2017-12-17 13:20:03 +08:00
|
|
|
iterate := ScanArgsOptionalInput(ec, args, &sepv)
|
2018-01-25 09:40:15 +08:00
|
|
|
sep := sepv
|
2017-12-17 13:20:03 +08:00
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
|
|
|
var buf bytes.Buffer
|
2018-01-01 04:31:45 +08:00
|
|
|
iterate(func(v types.Value) {
|
2018-01-25 09:40:15 +08:00
|
|
|
if s, ok := v.(string); ok {
|
2017-12-17 13:20:03 +08:00
|
|
|
if buf.Len() > 0 {
|
|
|
|
buf.WriteString(sep)
|
|
|
|
}
|
2018-01-25 09:40:15 +08:00
|
|
|
buf.WriteString(s)
|
2017-12-17 13:20:03 +08:00
|
|
|
} else {
|
2018-01-25 07:57:58 +08:00
|
|
|
throwf("join wants string input, got %s", types.Kind(v))
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
})
|
|
|
|
out := ec.ports[1].Chan
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- buf.String()
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// splits splits an argument strings by a delimiter and writes all pieces.
|
2018-01-01 04:31:45 +08:00
|
|
|
func splits(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2017-12-21 06:40:33 +08:00
|
|
|
var (
|
2018-01-25 09:40:15 +08:00
|
|
|
s, sep string
|
2017-12-21 06:40:33 +08:00
|
|
|
optMax int
|
|
|
|
)
|
2017-12-17 13:20:03 +08:00
|
|
|
ScanArgs(args, &sep, &s)
|
2018-01-25 09:40:15 +08:00
|
|
|
ScanOpts(opts, OptToScan{"max", &optMax, "-1"})
|
2017-12-17 13:20:03 +08:00
|
|
|
|
|
|
|
out := ec.ports[1].Chan
|
2018-01-25 09:40:15 +08:00
|
|
|
parts := strings.SplitN(s, sep, optMax)
|
2017-12-17 13:20:03 +08:00
|
|
|
for _, p := range parts {
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- p
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func replaces(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2017-12-17 13:20:03 +08:00
|
|
|
var (
|
2018-01-25 09:40:15 +08:00
|
|
|
old, repl, s string
|
2017-12-17 13:20:03 +08:00
|
|
|
optMax int
|
|
|
|
)
|
|
|
|
ScanArgs(args, &old, &repl, &s)
|
2018-01-25 09:40:15 +08:00
|
|
|
ScanOpts(opts, OptToScan{"max", &optMax, "-1"})
|
2017-12-17 13:20:03 +08:00
|
|
|
|
2018-01-25 09:40:15 +08:00
|
|
|
ec.ports[1].Chan <- strings.Replace(s, old, repl, optMax)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func ord(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2018-01-25 09:40:15 +08:00
|
|
|
var s string
|
2017-12-17 13:20:03 +08:00
|
|
|
ScanArgs(args, &s)
|
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
|
|
|
out := ec.ports[1].Chan
|
|
|
|
for _, r := range s {
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- "0x" + strconv.FormatInt(int64(r), 16)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrBadBase is thrown by the "base" builtin if the base is smaller than 2 or
|
|
|
|
// greater than 36.
|
|
|
|
var ErrBadBase = errors.New("bad base")
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func base(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2017-12-17 13:20:03 +08:00
|
|
|
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 {
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- strconv.FormatInt(int64(num), b)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func wcswidth(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2018-01-25 09:40:15 +08:00
|
|
|
var s string
|
2017-12-17 13:20:03 +08:00
|
|
|
ScanArgs(args, &s)
|
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
|
|
|
out := ec.ports[1].Chan
|
2018-01-25 09:40:15 +08:00
|
|
|
out <- strconv.Itoa(util.Wcswidth(s))
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func overrideWcwidth(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2017-12-17 13:20:03 +08:00
|
|
|
var (
|
2018-01-25 09:40:15 +08:00
|
|
|
s string
|
2017-12-17 13:20:03 +08:00
|
|
|
w int
|
|
|
|
)
|
|
|
|
ScanArgs(args, &s, &w)
|
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
|
|
|
r, err := toRune(s)
|
|
|
|
maybeThrow(err)
|
|
|
|
util.OverrideWcwidth(r, w)
|
|
|
|
}
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func hasPrefix(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2018-01-25 09:40:15 +08:00
|
|
|
var s, prefix string
|
2017-12-17 13:20:03 +08:00
|
|
|
ScanArgs(args, &s, &prefix)
|
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
2018-01-25 09:40:15 +08:00
|
|
|
ec.OutputChan() <- types.Bool(strings.HasPrefix(s, prefix))
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
|
2018-01-01 04:31:45 +08:00
|
|
|
func hasSuffix(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2018-01-25 09:40:15 +08:00
|
|
|
var s, suffix string
|
2017-12-17 13:20:03 +08:00
|
|
|
ScanArgs(args, &s, &suffix)
|
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
2018-01-25 09:40:15 +08:00
|
|
|
ec.OutputChan() <- types.Bool(strings.HasSuffix(s, suffix))
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2018-01-01 04:31:45 +08:00
|
|
|
func eawk(ec *Frame, args []types.Value, opts map[string]types.Value) {
|
2018-01-01 01:12:02 +08:00
|
|
|
var f Fn
|
2017-12-17 13:20:03 +08:00
|
|
|
iterate := ScanArgsOptionalInput(ec, args, &f)
|
|
|
|
TakeNoOpt(opts)
|
|
|
|
|
|
|
|
broken := false
|
2018-01-01 04:31:45 +08:00
|
|
|
iterate(func(v types.Value) {
|
2017-12-17 13:20:03 +08:00
|
|
|
if broken {
|
|
|
|
return
|
|
|
|
}
|
2018-01-25 09:40:15 +08:00
|
|
|
line, ok := v.(string)
|
2017-12-17 13:20:03 +08:00
|
|
|
if !ok {
|
|
|
|
throw(ErrInput)
|
|
|
|
}
|
2018-01-01 04:31:45 +08:00
|
|
|
args := []types.Value{line}
|
2018-01-25 09:40:15 +08:00
|
|
|
for _, field := range eawkWordSep.Split(strings.Trim(line, " \t"), -1) {
|
|
|
|
args = append(args, field)
|
2017-12-17 13:20:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|