github.com/hirochachacha/plua@v0.0.0-20170217012138-c82f520cc725/stdlib/string/gsub.go (about) 1 package string 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "strings" 8 9 "github.com/hirochachacha/plua/internal/arith" 10 "github.com/hirochachacha/plua/internal/pattern" 11 "github.com/hirochachacha/plua/object" 12 "github.com/hirochachacha/plua/object/fnutil" 13 ) 14 15 // gsub(s, pattern, repl, [, n]) 16 func gsub(th object.Thread, args ...object.Value) ([]object.Value, *object.RuntimeError) { 17 ap := fnutil.NewArgParser(th, args) 18 19 s, err := ap.ToGoString(0) 20 if err != nil { 21 return nil, err 22 } 23 24 pat, err := ap.ToGoString(1) 25 if err != nil { 26 return nil, err 27 } 28 29 repl, err := ap.ToValue(2) 30 if err != nil { 31 return nil, err 32 } 33 34 n, err := ap.OptGoInt(3, -1) 35 if err != nil { 36 return nil, err 37 } 38 39 switch repl := repl.(type) { 40 case object.GoFunction, object.Closure: 41 ret, count, e := gsubFunc(th, s, pat, repl, n) 42 if e != nil { 43 return nil, object.NewRuntimeError(e.Error()) 44 } 45 46 return []object.Value{object.String(ret), object.Integer(count)}, nil 47 case object.Table: 48 ret, count, e := gsubTable(th, s, pat, repl, n) 49 if e != nil { 50 return nil, object.NewRuntimeError(e.Error()) 51 } 52 53 return []object.Value{object.String(ret), object.Integer(count)}, nil 54 default: 55 if repl, ok := object.ToGoString(repl); ok { 56 ret, count, e := gsubStr(th, s, pat, repl, n) 57 if e != nil { 58 return nil, object.NewRuntimeError(e.Error()) 59 } 60 61 return []object.Value{object.String(ret), object.Integer(count)}, nil 62 } 63 64 return nil, ap.TypeError(2, "string/function/table") 65 } 66 } 67 68 func gsubFunc(th object.Thread, input, pat string, fn object.Value, n int) (string, int, error) { 69 return pattern.ReplaceAllFunc(input, pat, func(caps []pattern.Capture) (string, error) { 70 loc := caps[0] 71 72 if len(caps) > 1 { 73 caps = caps[1:] 74 } 75 rargs := make([]object.Value, len(caps)) 76 for i, cap := range caps { 77 rargs[i] = cap.Value(input) 78 } 79 80 rets, rerr := th.Call(fn, rargs...) 81 if rerr != nil { 82 return "", rerr 83 } 84 85 repl := loc.Value(input) 86 87 if len(rets) > 0 { 88 if val := rets[0]; val != nil && val != object.False { 89 var ok bool 90 repl, ok = object.ToString(val) 91 if !ok { 92 return "", fmt.Errorf("invalid replacement value (a %s)", object.ToType(val)) 93 } 94 } 95 } 96 97 return repl.String(), nil 98 }, n) 99 } 100 101 func gsubTable(th object.Thread, input, pat string, t object.Table, n int) (string, int, error) { 102 return pattern.ReplaceAllFunc(input, pat, func(caps []pattern.Capture) (string, error) { 103 loc := caps[0] 104 105 repl := loc.Value(input) 106 107 key := repl 108 if len(caps) > 1 { 109 key = caps[1].Value(input) 110 } 111 112 val, err := arith.CallGettable(th, t, key) 113 if err != nil { 114 return "", err 115 } 116 117 if val != nil && val != object.False { 118 var ok bool 119 repl, ok = object.ToString(val) 120 if !ok { 121 return "", fmt.Errorf("invalid replacement value (a %s)", object.ToType(val)) 122 } 123 } 124 125 return repl.String(), nil 126 }, n) 127 } 128 129 func gsubStr(th object.Thread, input, pat, repl string, n int) (string, int, error) { 130 parts, e := gsubParseRepl(repl) 131 if e != nil { 132 return "", -1, e 133 } 134 135 var buf bytes.Buffer 136 137 return pattern.ReplaceAllFunc(input, pat, func(caps []pattern.Capture) (string, error) { 138 loc := caps[0] 139 140 if len(caps) == 1 { 141 caps = append(caps, loc) 142 } 143 144 buf.Reset() 145 146 for _, part := range parts { 147 if part[0] == '%' { 148 if part[1] == '%' { 149 buf.WriteByte('%') 150 } else { 151 j := int(part[1] - '0') 152 if j >= len(caps) { 153 return "", fmt.Errorf("invalid capture index %%%d", j) 154 } 155 156 cap := caps[j] 157 158 buf.WriteString(cap.Value(input).String()) 159 } 160 } else { 161 buf.WriteString(part) 162 } 163 } 164 165 return buf.String(), nil 166 }, n) 167 } 168 169 func gsubParseRepl(repl string) (parts []string, err error) { 170 for { 171 i := strings.IndexByte(repl, '%') 172 if i == -1 { 173 if repl != "" { 174 parts = append(parts, repl) 175 } 176 177 break 178 } 179 180 if i != 0 { 181 parts = append(parts, repl[:i]) 182 } 183 184 if i == len(repl)-1 { 185 return nil, errors.New("invalid use of '%' in replacement string") 186 } 187 188 d := repl[i+1] 189 if !('0' <= d && d <= '9') && d != '%' { 190 return nil, errors.New("invalid use of '%' in replacement string") 191 } 192 193 parts = append(parts, repl[i:i+2]) 194 195 repl = repl[i+2:] 196 } 197 198 return parts, nil 199 }