elvish/pkg/eval/builtin_fn_str.go

176 lines
3.8 KiB
Go
Raw Normal View History

2017-12-17 13:20:03 +08:00
package eval
import (
"bytes"
"errors"
"fmt"
2017-12-17 13:20:03 +08:00
"regexp"
"strconv"
"strings"
"unicode/utf8"
2017-12-17 13:20:03 +08:00
2019-12-24 04:00:59 +08:00
"github.com/elves/elvish/pkg/eval/vals"
"github.com/elves/elvish/pkg/util"
2017-12-17 13:20:03 +08:00
)
// String operations.
2019-04-19 05:15:34 +08:00
// ErrInputOfEawkMustBeString is thrown when eawk gets a non-string input.
var ErrInputOfEawkMustBeString = errors.New("input of eawk must be string")
2017-12-17 13:20:03 +08:00
func init() {
2018-02-07 03:39:40 +08:00
addBuiltinFns(map[string]interface{}{
"<s": func(a, b string) bool { return a < b },
"<=s": func(a, b string) bool { return a <= b },
"==s": func(a, b string) bool { return a == b },
"!=s": func(a, b string) bool { return a != b },
">s": func(a, b string) bool { return a > b },
">=s": func(a, b string) bool { return a >= b },
"to-string": toString,
"ord": ord,
"chr": chr,
"base": base,
"wcswidth": util.Wcswidth,
"-override-wcwidth": util.OverrideWcwidth,
"has-prefix": strings.HasPrefix,
"has-suffix": strings.HasSuffix,
2017-12-17 13:20:03 +08:00
2018-02-05 15:35:49 +08:00
"joins": joins,
"splits": splits,
"replaces": replaces,
2017-12-17 13:20:03 +08:00
2018-02-05 15:35:49 +08:00
"eawk": eawk,
2017-12-17 13:20:03 +08:00
})
}
// toString converts all arguments to strings.
func toString(fm *Frame, args ...interface{}) {
out := fm.OutputChan()
2017-12-17 13:20:03 +08:00
for _, a := range args {
2018-02-15 17:14:05 +08:00
out <- vals.ToString(a)
2017-12-17 13:20:03 +08:00
}
}
// joins joins all input strings with a delimiter.
2018-09-27 08:48:44 +08:00
func joins(sep string, inputs Inputs) (string, error) {
2017-12-17 13:20:03 +08:00
var buf bytes.Buffer
2018-09-27 08:48:44 +08:00
var errJoin error
first := true
2018-02-05 15:35:49 +08:00
inputs(func(v interface{}) {
2018-09-27 08:48:44 +08:00
if errJoin != nil {
return
}
if s, ok := v.(string); ok {
if first {
first = false
} else {
2017-12-17 13:20:03 +08:00
buf.WriteString(sep)
}
buf.WriteString(s)
2017-12-17 13:20:03 +08:00
} else {
2018-09-27 08:48:44 +08:00
errJoin = fmt.Errorf("join wants string input, got %s", vals.Kind(v))
2017-12-17 13:20:03 +08:00
}
})
2018-09-27 08:48:44 +08:00
return buf.String(), errJoin
2017-12-17 13:20:03 +08:00
}
type maxOpt struct{ Max int }
func (o *maxOpt) SetDefaultOptions() { o.Max = -1 }
2018-02-05 15:35:49 +08:00
// splits splits an argument strings by a delimiter and writes all pieces.
func splits(fm *Frame, opts maxOpt, sep, s string) {
2018-02-05 15:35:49 +08:00
out := fm.ports[1].Chan
parts := strings.SplitN(s, sep, opts.Max)
2017-12-17 13:20:03 +08:00
for _, p := range parts {
out <- p
2017-12-17 13:20:03 +08:00
}
}
func replaces(opts maxOpt, old, repl, s string) string {
return strings.Replace(s, old, repl, opts.Max)
2017-12-17 13:20:03 +08:00
}
func ord(fm *Frame, s string) {
out := fm.ports[1].Chan
2017-12-17 13:20:03 +08:00
for _, r := range s {
out <- "0x" + strconv.FormatInt(int64(r), 16)
2017-12-17 13:20:03 +08:00
}
}
func chr(nums ...int) (string, error) {
var b bytes.Buffer
for _, num := range nums {
if !utf8.ValidRune(rune(num)) {
return "", fmt.Errorf("Invalid codepoint: %d", num)
}
b.WriteRune(rune(num))
}
return b.String(), nil
}
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")
func base(fm *Frame, b int, nums ...int) error {
2017-12-17 13:20:03 +08:00
if b < 2 || b > 36 {
return ErrBadBase
2017-12-17 13:20:03 +08:00
}
out := fm.ports[1].Chan
2017-12-17 13:20:03 +08:00
for _, num := range nums {
out <- strconv.FormatInt(int64(num), b)
2017-12-17 13:20:03 +08:00
}
return nil
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.
func eawk(fm *Frame, f Callable, inputs Inputs) error {
2017-12-17 13:20:03 +08:00
broken := false
var err error
2018-02-05 15:35:49 +08:00
inputs(func(v interface{}) {
2017-12-17 13:20:03 +08:00
if broken {
return
}
line, ok := v.(string)
2017-12-17 13:20:03 +08:00
if !ok {
broken = true
2019-04-19 05:15:34 +08:00
err = ErrInputOfEawkMustBeString
return
2017-12-17 13:20:03 +08:00
}
args := []interface{}{line}
for _, field := range eawkWordSep.Split(strings.Trim(line, " \t"), -1) {
args = append(args, field)
2017-12-17 13:20:03 +08:00
}
newFm := fm.fork("fn of eawk")
// TODO: Close port 0 of newFm.
ex := newFm.Call(f, args, NoOpts)
newFm.Close()
2017-12-17 13:20:03 +08:00
if ex != nil {
switch Cause(ex) {
2017-12-17 13:20:03 +08:00
case nil, Continue:
// nop
case Break:
broken = true
default:
broken = true
err = ex
2017-12-17 13:20:03 +08:00
}
}
})
return err
2017-12-17 13:20:03 +08:00
}