forked from elves/elvish
/
builtin_fn_str.go
157 lines (133 loc) · 3.37 KB
/
builtin_fn_str.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package eval
import (
"bytes"
"errors"
"regexp"
"strconv"
"strings"
"github.com/elves/elvish/eval/vals"
"github.com/elves/elvish/util"
)
// String operations.
var ErrInput = errors.New("input error")
func init() {
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,
"base": base,
"wcswidth": util.Wcswidth,
"-override-wcwidth": util.OverrideWcwidth,
"has-prefix": strings.HasPrefix,
"has-suffix": strings.HasSuffix,
"joins": joins,
"splits": splits,
"replaces": replaces,
"eawk": eawk,
})
}
// toString converts all arguments to strings.
func toString(fm *Frame, args ...interface{}) {
out := fm.OutputChan()
for _, a := range args {
out <- vals.ToString(a)
}
}
// joins joins all input strings with a delimiter.
func joins(sep string, inputs Inputs) string {
var buf bytes.Buffer
first := true
inputs(func(v interface{}) {
if s, ok := v.(string); ok {
if first {
first = false
} else {
buf.WriteString(sep)
}
buf.WriteString(s)
} else {
throwf("join wants string input, got %s", vals.Kind(v))
}
})
return buf.String()
}
// splits splits an argument strings by a delimiter and writes all pieces.
func splits(fm *Frame, rawOpts RawOptions, sep, s string) {
opts := struct{ Max int }{-1}
rawOpts.Scan(&opts)
out := fm.ports[1].Chan
parts := strings.SplitN(s, sep, opts.Max)
for _, p := range parts {
out <- p
}
}
func replaces(rawOpts RawOptions, old, repl, s string) string {
opts := struct{ Max int }{-1}
rawOpts.Scan(&opts)
return strings.Replace(s, old, repl, opts.Max)
}
func ord(fm *Frame, s string) {
out := fm.ports[1].Chan
for _, r := range s {
out <- "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(fm *Frame, b int, nums ...int) error {
if b < 2 || b > 36 {
return ErrBadBase
}
out := fm.ports[1].Chan
for _, num := range nums {
out <- strconv.FormatInt(int64(num), b)
}
return nil
}
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 {
broken := false
var err error
inputs(func(v interface{}) {
if broken {
return
}
line, ok := v.(string)
if !ok {
broken = true
err = ErrInput
return
}
args := []interface{}{line}
for _, field := range eawkWordSep.Split(strings.Trim(line, " \t"), -1) {
args = append(args, field)
}
newFm := fm.fork("fn of eawk")
// TODO: Close port 0 of newFm.
ex := newFm.Call(f, args, NoOpts)
newFm.Close()
if ex != nil {
switch ex.(*Exception).Cause {
case nil, Continue:
// nop
case Break:
broken = true
default:
broken = true
err = ex
}
}
})
return err
}