src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/mods/re/re.go (about)

     1  // Package re implements a regular expression module.
     2  package re
     3  
     4  import (
     5  	"regexp"
     6  
     7  	"src.elv.sh/pkg/eval"
     8  	"src.elv.sh/pkg/eval/errs"
     9  	"src.elv.sh/pkg/eval/vals"
    10  )
    11  
    12  // Ns is the namespace for the re: module.
    13  var Ns = eval.BuildNsNamed("re").
    14  	AddGoFns(map[string]any{
    15  		"quote":   regexp.QuoteMeta,
    16  		"match":   match,
    17  		"find":    find,
    18  		"replace": replace,
    19  		"split":   split,
    20  		"awk":     eval.Eawk,
    21  	}).Ns()
    22  
    23  type matchOpts struct{ Posix bool }
    24  
    25  func (*matchOpts) SetDefaultOptions() {}
    26  
    27  func match(opts matchOpts, argPattern, source string) (bool, error) {
    28  	pattern, err := makePattern(argPattern, opts.Posix, false)
    29  	if err != nil {
    30  		return false, err
    31  	}
    32  	return pattern.MatchString(source), nil
    33  }
    34  
    35  // Struct for holding options to find. Also used by split.
    36  type findOpts struct {
    37  	Posix   bool
    38  	Longest bool
    39  	Max     int
    40  }
    41  
    42  func (o *findOpts) SetDefaultOptions() { o.Max = -1 }
    43  
    44  func find(fm *eval.Frame, opts findOpts, argPattern, source string) error {
    45  	out := fm.ValueOutput()
    46  
    47  	pattern, err := makePattern(argPattern, opts.Posix, opts.Longest)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	matches := pattern.FindAllSubmatchIndex([]byte(source), opts.Max)
    52  
    53  	for _, match := range matches {
    54  		start, end := match[0], match[1]
    55  		groups := vals.EmptyList
    56  		for i := 0; i < len(match); i += 2 {
    57  			start, end := match[i], match[i+1]
    58  			text := ""
    59  			// FindAllSubmatchIndex may return negative indices to indicate
    60  			// that the pattern didn't appear in the text.
    61  			if start >= 0 && end >= 0 {
    62  				text = source[start:end]
    63  			}
    64  			groups = groups.Conj(submatchStruct{text, start, end})
    65  		}
    66  		err := out.Put(matchStruct{source[start:end], start, end, groups})
    67  		if err != nil {
    68  			return err
    69  		}
    70  	}
    71  	return nil
    72  }
    73  
    74  type replaceOpts struct {
    75  	Posix   bool
    76  	Longest bool
    77  	Literal bool
    78  }
    79  
    80  func (*replaceOpts) SetDefaultOptions() {}
    81  
    82  func replace(fm *eval.Frame, opts replaceOpts, argPattern string, argRepl any, source string) (string, error) {
    83  
    84  	pattern, err := makePattern(argPattern, opts.Posix, opts.Longest)
    85  	if err != nil {
    86  		return "", err
    87  	}
    88  
    89  	if opts.Literal {
    90  		repl, ok := argRepl.(string)
    91  		if !ok {
    92  			return "", &errs.BadValue{What: "literal replacement",
    93  				Valid: "string", Actual: vals.Kind(argRepl)}
    94  		}
    95  		return pattern.ReplaceAllLiteralString(source, repl), nil
    96  	}
    97  	switch repl := argRepl.(type) {
    98  	case string:
    99  		return pattern.ReplaceAllString(source, repl), nil
   100  	case eval.Callable:
   101  		var errReplace error
   102  		replFunc := func(s string) string {
   103  			if errReplace != nil {
   104  				return ""
   105  			}
   106  			values, err := fm.CaptureOutput(func(fm *eval.Frame) error {
   107  				return repl.Call(fm, []any{s}, eval.NoOpts)
   108  			})
   109  			if err != nil {
   110  				errReplace = err
   111  				return ""
   112  			}
   113  			if len(values) != 1 {
   114  				errReplace = &errs.ArityMismatch{What: "replacement function output",
   115  					ValidLow: 1, ValidHigh: 1, Actual: len(values)}
   116  				return ""
   117  			}
   118  			output, ok := values[0].(string)
   119  			if !ok {
   120  				errReplace = &errs.BadValue{What: "replacement function output",
   121  					Valid: "string", Actual: vals.Kind(values[0])}
   122  				return ""
   123  			}
   124  			return output
   125  		}
   126  		return pattern.ReplaceAllStringFunc(source, replFunc), errReplace
   127  	default:
   128  		return "", &errs.BadValue{What: "replacement",
   129  			Valid: "string or function", Actual: vals.Kind(argRepl)}
   130  	}
   131  }
   132  
   133  func split(fm *eval.Frame, opts findOpts, argPattern, source string) error {
   134  	out := fm.ValueOutput()
   135  
   136  	pattern, err := makePattern(argPattern, opts.Posix, opts.Longest)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	pieces := pattern.Split(source, opts.Max)
   142  	for _, piece := range pieces {
   143  		err := out.Put(piece)
   144  		if err != nil {
   145  			return err
   146  		}
   147  	}
   148  	return nil
   149  }
   150  
   151  func makePattern(p string, posix, longest bool) (*regexp.Regexp, error) {
   152  	pattern, err := compile(p, posix)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	if longest {
   157  		pattern.Longest()
   158  	}
   159  	return pattern, nil
   160  }
   161  
   162  func compile(pattern string, posix bool) (*regexp.Regexp, error) {
   163  	if posix {
   164  		return regexp.CompilePOSIX(pattern)
   165  	}
   166  	return regexp.Compile(pattern)
   167  }