github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/eval/re/re.go (about)

     1  package re
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  	"strconv"
     7  
     8  	"github.com/elves/elvish/eval"
     9  	"github.com/elves/elvish/util"
    10  	"github.com/xiaq/persistent/vector"
    11  )
    12  
    13  func Namespace() eval.Namespace {
    14  	ns := eval.Namespace{}
    15  	eval.AddBuiltinFns(ns, fns...)
    16  	return ns
    17  }
    18  
    19  var fns = []*eval.BuiltinFn{
    20  	{"quote", eval.WrapStringToString(regexp.QuoteMeta)},
    21  	{"match", match},
    22  	{"find", find},
    23  	{"replace", replace},
    24  	{"split", split},
    25  }
    26  
    27  func match(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
    28  	out := ec.OutputChan()
    29  	var (
    30  		argPattern eval.String
    31  		argSource  eval.String
    32  		optPOSIX   eval.Bool
    33  	)
    34  	eval.ScanArgs(args, &argPattern, &argSource)
    35  	eval.ScanOpts(opts, eval.Opt{"posix", &optPOSIX, eval.Bool(false)})
    36  
    37  	pattern := makePattern(argPattern, optPOSIX, eval.Bool(false))
    38  	matched := pattern.MatchString(string(argSource))
    39  	out <- eval.Bool(matched)
    40  }
    41  
    42  var (
    43  	matchFields    = []string{"text", "start", "end", "groups"}
    44  	submatchFields = []string{"text", "start", "end"}
    45  )
    46  
    47  func find(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
    48  	out := ec.OutputChan()
    49  	var (
    50  		argPattern eval.String
    51  		argSource  eval.String
    52  		optPOSIX   eval.Bool
    53  		optLongest eval.Bool
    54  		optMax     int
    55  	)
    56  	eval.ScanArgs(args, &argPattern, &argSource)
    57  	eval.ScanOpts(opts,
    58  		eval.Opt{"posix", &optPOSIX, eval.Bool(false)},
    59  		eval.Opt{"longest", &optLongest, eval.Bool(false)},
    60  		eval.Opt{"max", &optMax, eval.String("-1")})
    61  
    62  	pattern := makePattern(argPattern, optPOSIX, optLongest)
    63  
    64  	matches := pattern.FindAllSubmatchIndex([]byte(argSource), optMax)
    65  	for _, match := range matches {
    66  		start, end := match[0], match[1]
    67  		groups := vector.Empty
    68  		for i := 0; i < len(match); i += 2 {
    69  			start, end := match[i], match[i+1]
    70  			groups = groups.Cons(&eval.Struct{submatchFields, []eval.Variable{
    71  				eval.NewRoVariable(argSource[start:end]),
    72  				eval.NewRoVariable(eval.String(strconv.Itoa(start))),
    73  				eval.NewRoVariable(eval.String(strconv.Itoa(end))),
    74  			}})
    75  		}
    76  		out <- &eval.Struct{matchFields, []eval.Variable{
    77  			eval.NewRoVariable(argSource[start:end]),
    78  			eval.NewRoVariable(eval.String(strconv.Itoa(start))),
    79  			eval.NewRoVariable(eval.String(strconv.Itoa(end))),
    80  			eval.NewRoVariable(eval.NewListFromVector(groups)),
    81  		}}
    82  	}
    83  }
    84  
    85  func replace(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
    86  	out := ec.OutputChan()
    87  	var (
    88  		argPattern eval.String
    89  		argRepl    eval.Value
    90  		argSource  eval.String
    91  		optPOSIX   eval.Bool
    92  		optLongest eval.Bool
    93  		optLiteral eval.Bool
    94  	)
    95  	eval.ScanArgs(args, &argPattern, &argRepl, &argSource)
    96  	eval.ScanOpts(opts,
    97  		eval.Opt{"posix", &optPOSIX, eval.Bool(false)},
    98  		eval.Opt{"longest", &optLongest, eval.Bool(false)},
    99  		eval.Opt{"literal", &optLiteral, eval.Bool(false)})
   100  
   101  	pattern := makePattern(argPattern, optPOSIX, optLongest)
   102  
   103  	var result string
   104  	if optLiteral {
   105  		repl, ok := argRepl.(eval.String)
   106  		if !ok {
   107  			throwf("replacement must be string when literal is set, got %s",
   108  				argRepl.Kind())
   109  		}
   110  		result = pattern.ReplaceAllLiteralString(string(argSource), string(repl))
   111  	} else {
   112  		switch repl := argRepl.(type) {
   113  		case eval.String:
   114  			result = pattern.ReplaceAllString(string(argSource), string(repl))
   115  		case eval.CallableValue:
   116  			replFunc := func(s string) string {
   117  				values, err := ec.PCaptureOutput(repl,
   118  					[]eval.Value{eval.String(s)}, eval.NoOpts)
   119  				maybeThrow(err)
   120  				if len(values) != 1 {
   121  					throwf("replacement function must output exactly one value, got %d", len(values))
   122  				}
   123  				output, ok := values[0].(eval.String)
   124  				if !ok {
   125  					throwf("replacement function must output one string, got %s", values[0].Kind())
   126  				}
   127  				return string(output)
   128  			}
   129  			result = pattern.ReplaceAllStringFunc(string(argSource), replFunc)
   130  		default:
   131  			throwf("replacement must be string or function, got %s",
   132  				argRepl.Kind())
   133  		}
   134  	}
   135  	out <- eval.String(result)
   136  }
   137  
   138  func split(ec *eval.EvalCtx, args []eval.Value, opts map[string]eval.Value) {
   139  	out := ec.OutputChan()
   140  	var (
   141  		argPattern eval.String
   142  		argSource  eval.String
   143  		optPOSIX   eval.Bool
   144  		optLongest eval.Bool
   145  		optMax     int
   146  	)
   147  	eval.ScanArgs(args, &argPattern, &argSource)
   148  	eval.ScanOpts(opts,
   149  		eval.Opt{"posix", &optPOSIX, eval.Bool(false)},
   150  		eval.Opt{"longest", &optLongest, eval.Bool(false)},
   151  		eval.Opt{"max", &optMax, eval.String("-1")})
   152  
   153  	pattern := makePattern(argPattern, optPOSIX, optLongest)
   154  
   155  	pieces := pattern.Split(string(argSource), optMax)
   156  	for _, piece := range pieces {
   157  		out <- eval.String(piece)
   158  	}
   159  }
   160  
   161  func makePattern(argPattern eval.String,
   162  	optPOSIX, optLongest eval.Bool) *regexp.Regexp {
   163  
   164  	var (
   165  		pattern *regexp.Regexp
   166  		err     error
   167  	)
   168  	if optPOSIX {
   169  		pattern, err = regexp.CompilePOSIX(string(argPattern))
   170  	} else {
   171  		pattern, err = regexp.Compile(string(argPattern))
   172  	}
   173  	maybeThrow(err)
   174  	if optLongest {
   175  		pattern.Longest()
   176  	}
   177  	return pattern
   178  }
   179  
   180  func throwf(format string, args ...interface{}) {
   181  	util.Throw(fmt.Errorf(format, args...))
   182  }
   183  
   184  func maybeThrow(err error) {
   185  	if err != nil {
   186  		util.Throw(err)
   187  	}
   188  }