mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-11-28 07:21:21 +08:00
27495be1b9
- Use standard error types. - Support float64 that stores an integer. - Don't incur unnecessary overhead for int arguments.
169 lines
3.5 KiB
Go
169 lines
3.5 KiB
Go
package eval
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"src.elv.sh/pkg/eval/errs"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
"src.elv.sh/pkg/wcwidth"
|
|
)
|
|
|
|
// String operations.
|
|
|
|
// TODO(xiaq): Document -override-wcswidth.
|
|
|
|
func init() {
|
|
addBuiltinFns(map[string]any{
|
|
"<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,
|
|
|
|
"base": base,
|
|
|
|
"wcswidth": wcwidth.Of,
|
|
"-override-wcwidth": wcwidth.Override,
|
|
|
|
"eawk": Eawk,
|
|
})
|
|
}
|
|
|
|
func toString(fm *Frame, args ...any) error {
|
|
out := fm.ValueOutput()
|
|
for _, a := range args {
|
|
err := out.Put(vals.ToString(a))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func base(fm *Frame, b int, nums ...vals.Num) error {
|
|
if b < 2 || b > 36 {
|
|
return errs.OutOfRange{What: "base",
|
|
ValidLow: "2", ValidHigh: "36", Actual: strconv.Itoa(b)}
|
|
}
|
|
// Don't output anything yet in case some arguments are invalid.
|
|
results := make([]string, len(nums))
|
|
for i, num := range nums {
|
|
switch num := num.(type) {
|
|
case int:
|
|
results[i] = strconv.FormatInt(int64(num), b)
|
|
case *big.Int:
|
|
results[i] = num.Text(b)
|
|
case float64:
|
|
if i64 := int64(num); float64(i64) == num {
|
|
results[i] = strconv.FormatInt(i64, b)
|
|
} else if num == math.Trunc(num) {
|
|
var z big.Int
|
|
z.SetString(fmt.Sprintf("%.0f", num), 10)
|
|
results[i] = z.Text(b)
|
|
} else {
|
|
return errs.BadValue{What: "number",
|
|
Valid: "integer", Actual: vals.ReprPlain(num)}
|
|
}
|
|
default:
|
|
return errs.BadValue{What: "number",
|
|
Valid: "integer", Actual: vals.ReprPlain(num)}
|
|
}
|
|
}
|
|
|
|
out := fm.ValueOutput()
|
|
for _, s := range results {
|
|
err := out.Put(s)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ErrInputOfEawkMustBeString is thrown when eawk gets a non-string input.
|
|
//
|
|
// TODO: Change the message to say re:awk when eawk is removed.
|
|
var ErrInputOfEawkMustBeString = errors.New("input of eawk must be string")
|
|
|
|
type eawkOpt struct {
|
|
Sep string
|
|
SepPosix bool
|
|
SepLongest bool
|
|
}
|
|
|
|
func (o *eawkOpt) SetDefaultOptions() {
|
|
o.Sep = "[ \t]+"
|
|
}
|
|
|
|
// Eawk implements the re:awk command and the deprecated eawk command. It is
|
|
// put in this package and exported since this package can't depend on
|
|
// src.elv.sh/pkg/mods/re.
|
|
func Eawk(fm *Frame, opts eawkOpt, f Callable, inputs Inputs) error {
|
|
wordSep, err := makePattern(opts.Sep, opts.SepPosix, opts.SepLongest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
broken := false
|
|
inputs(func(v any) {
|
|
if broken {
|
|
return
|
|
}
|
|
line, ok := v.(string)
|
|
if !ok {
|
|
broken = true
|
|
err = ErrInputOfEawkMustBeString
|
|
return
|
|
}
|
|
args := []any{line}
|
|
for _, field := range wordSep.Split(strings.Trim(line, " \t"), -1) {
|
|
args = append(args, field)
|
|
}
|
|
|
|
newFm := fm.Fork("fn of eawk")
|
|
// TODO: Close port 0 of newFm.
|
|
ex := f.Call(newFm, args, NoOpts)
|
|
newFm.Close()
|
|
|
|
if ex != nil {
|
|
switch Reason(ex) {
|
|
case nil, Continue:
|
|
// nop
|
|
case Break:
|
|
broken = true
|
|
default:
|
|
broken = true
|
|
err = ex
|
|
}
|
|
}
|
|
})
|
|
return err
|
|
}
|
|
|
|
func makePattern(p string, posix, longest bool) (*regexp.Regexp, error) {
|
|
pattern, err := compilePattern(p, posix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if longest {
|
|
pattern.Longest()
|
|
}
|
|
return pattern, nil
|
|
}
|
|
|
|
func compilePattern(pattern string, posix bool) (*regexp.Regexp, error) {
|
|
if posix {
|
|
return regexp.CompilePOSIX(pattern)
|
|
}
|
|
return regexp.Compile(pattern)
|
|
}
|