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  }