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  }