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 }