src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/eval/builtin_fn_str.go (about) 1 package eval 2 3 import ( 4 "errors" 5 "fmt" 6 "math" 7 "math/big" 8 "regexp" 9 "strconv" 10 "strings" 11 12 "src.elv.sh/pkg/eval/errs" 13 "src.elv.sh/pkg/eval/vals" 14 "src.elv.sh/pkg/wcwidth" 15 ) 16 17 // String operations. 18 19 // TODO(xiaq): Document -override-wcswidth. 20 21 func init() { 22 addBuiltinFns(map[string]any{ 23 "<s": chainStringComparer(func(a, b string) bool { return a < b }), 24 "<=s": chainStringComparer(func(a, b string) bool { return a <= b }), 25 "==s": chainStringComparer(func(a, b string) bool { return a == b }), 26 ">s": chainStringComparer(func(a, b string) bool { return a > b }), 27 ">=s": chainStringComparer(func(a, b string) bool { return a >= b }), 28 "!=s": func(a, b string) bool { return a != b }, 29 30 "to-string": toString, 31 32 "base": base, 33 34 "wcswidth": wcwidth.Of, 35 "-override-wcwidth": wcwidth.Override, 36 37 "eawk": Eawk, 38 }) 39 } 40 41 func chainStringComparer(p func(a, b string) bool) func(...string) bool { 42 return func(s ...string) bool { 43 for i := 0; i < len(s)-1; i++ { 44 if !p(s[i], s[i+1]) { 45 return false 46 } 47 } 48 return true 49 } 50 } 51 52 func toString(fm *Frame, args ...any) error { 53 out := fm.ValueOutput() 54 for _, a := range args { 55 err := out.Put(vals.ToString(a)) 56 if err != nil { 57 return err 58 } 59 } 60 return nil 61 } 62 63 func base(fm *Frame, b int, nums ...vals.Num) error { 64 if b < 2 || b > 36 { 65 return errs.OutOfRange{What: "base", 66 ValidLow: "2", ValidHigh: "36", Actual: strconv.Itoa(b)} 67 } 68 // Don't output anything yet in case some arguments are invalid. 69 results := make([]string, len(nums)) 70 for i, num := range nums { 71 switch num := num.(type) { 72 case int: 73 results[i] = strconv.FormatInt(int64(num), b) 74 case *big.Int: 75 results[i] = num.Text(b) 76 case float64: 77 if i64 := int64(num); float64(i64) == num { 78 results[i] = strconv.FormatInt(i64, b) 79 } else if num == math.Trunc(num) { 80 var z big.Int 81 z.SetString(fmt.Sprintf("%.0f", num), 10) 82 results[i] = z.Text(b) 83 } else { 84 return errs.BadValue{What: "number", 85 Valid: "integer", Actual: vals.ReprPlain(num)} 86 } 87 default: 88 return errs.BadValue{What: "number", 89 Valid: "integer", Actual: vals.ReprPlain(num)} 90 } 91 } 92 93 out := fm.ValueOutput() 94 for _, s := range results { 95 err := out.Put(s) 96 if err != nil { 97 return err 98 } 99 } 100 return nil 101 } 102 103 // ErrInputOfEawkMustBeString is thrown when eawk gets a non-string input. 104 // 105 // TODO: Change the message to say re:awk when eawk is removed. 106 var ErrInputOfEawkMustBeString = errors.New("input of eawk must be string") 107 108 type eawkOpt struct { 109 Sep string 110 SepPosix bool 111 SepLongest bool 112 } 113 114 func (o *eawkOpt) SetDefaultOptions() { 115 o.Sep = "[ \t]+" 116 } 117 118 // Eawk implements the re:awk command and the deprecated eawk command. It is 119 // put in this package and exported since this package can't depend on 120 // src.elv.sh/pkg/mods/re. 121 func Eawk(fm *Frame, opts eawkOpt, f Callable, inputs Inputs) error { 122 wordSep, err := makePattern(opts.Sep, opts.SepPosix, opts.SepLongest) 123 if err != nil { 124 return err 125 } 126 127 broken := false 128 inputs(func(v any) { 129 if broken { 130 return 131 } 132 line, ok := v.(string) 133 if !ok { 134 broken = true 135 err = ErrInputOfEawkMustBeString 136 return 137 } 138 args := []any{line} 139 for _, field := range wordSep.Split(strings.Trim(line, " \t"), -1) { 140 args = append(args, field) 141 } 142 143 newFm := fm.Fork("fn of eawk") 144 // TODO: Close port 0 of newFm. 145 ex := f.Call(newFm, args, NoOpts) 146 newFm.Close() 147 148 if ex != nil { 149 switch Reason(ex) { 150 case nil, Continue: 151 // nop 152 case Break: 153 broken = true 154 default: 155 broken = true 156 err = ex 157 } 158 } 159 }) 160 return err 161 } 162 163 func makePattern(p string, posix, longest bool) (*regexp.Regexp, error) { 164 pattern, err := compilePattern(p, posix) 165 if err != nil { 166 return nil, err 167 } 168 if longest { 169 pattern.Longest() 170 } 171 return pattern, nil 172 } 173 174 func compilePattern(pattern string, posix bool) (*regexp.Regexp, error) { 175 if posix { 176 return regexp.CompilePOSIX(pattern) 177 } 178 return regexp.Compile(pattern) 179 }