src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/edit/filter/compile.go (about) 1 // Package filter implements the Elvish filter DSL. 2 // 3 // The filter DSL is a subset of Elvish's expression syntax, and is useful for 4 // filtering a list of items. It is currently used in the listing modes of the 5 // interactive editor. 6 package filter 7 8 import ( 9 "errors" 10 "regexp" 11 "strings" 12 13 "src.elv.sh/pkg/errutil" 14 "src.elv.sh/pkg/parse" 15 "src.elv.sh/pkg/parse/cmpd" 16 ) 17 18 // Compile parses and compiles a filter. 19 func Compile(q string) (Filter, error) { 20 qn, errParse := parseFilter(q) 21 filter, errCompile := compileFilter(qn) 22 return filter, errutil.Multi(errParse, errCompile) 23 } 24 25 func parseFilter(q string) (*parse.Filter, error) { 26 qn := &parse.Filter{} 27 err := parse.ParseAs(parse.Source{Name: "[filter]", Code: q}, qn, parse.Config{}) 28 return qn, err 29 } 30 31 func compileFilter(qn *parse.Filter) (Filter, error) { 32 if len(qn.Opts) > 0 { 33 return nil, notSupportedError{"option"} 34 } 35 qs, err := compileCompounds(qn.Args) 36 if err != nil { 37 return nil, err 38 } 39 return andFilter{qs}, nil 40 } 41 42 func compileCompounds(ns []*parse.Compound) ([]Filter, error) { 43 qs := make([]Filter, len(ns)) 44 for i, n := range ns { 45 q, err := compileCompound(n) 46 if err != nil { 47 return nil, err 48 } 49 qs[i] = q 50 } 51 return qs, nil 52 } 53 54 func compileCompound(n *parse.Compound) (Filter, error) { 55 if pn, ok := cmpd.Primary(n); ok { 56 switch pn.Type { 57 case parse.Bareword, parse.SingleQuoted, parse.DoubleQuoted: 58 s := pn.Value 59 ignoreCase := s == strings.ToLower(s) 60 return substringFilter{s, ignoreCase}, nil 61 case parse.List: 62 return compileList(pn.Elements) 63 } 64 } 65 return nil, notSupportedError{cmpd.Shape(n)} 66 } 67 68 var errEmptySubfilter = errors.New("empty subfilter") 69 70 func compileList(elems []*parse.Compound) (Filter, error) { 71 if len(elems) == 0 { 72 return nil, errEmptySubfilter 73 } 74 head, ok := cmpd.StringLiteral(elems[0]) 75 if !ok { 76 return nil, notSupportedError{"non-literal subfilter head"} 77 } 78 switch head { 79 case "re": 80 if len(elems) == 1 { 81 return nil, notSupportedError{"re subfilter with no argument"} 82 } 83 if len(elems) > 2 { 84 return nil, notSupportedError{"re subfilter with two or more arguments"} 85 } 86 arg := elems[1] 87 s, ok := cmpd.StringLiteral(arg) 88 if !ok { 89 return nil, notSupportedError{"re subfilter with " + cmpd.Shape(arg)} 90 } 91 p, err := regexp.Compile(s) 92 if err != nil { 93 return nil, err 94 } 95 return regexpFilter{p}, nil 96 case "and": 97 qs, err := compileCompounds(elems[1:]) 98 if err != nil { 99 return nil, err 100 } 101 return andFilter{qs}, nil 102 case "or": 103 qs, err := compileCompounds(elems[1:]) 104 if err != nil { 105 return nil, err 106 } 107 return orFilter{qs}, nil 108 default: 109 return nil, notSupportedError{"head " + parse.SourceText(elems[0])} 110 } 111 } 112 113 type notSupportedError struct{ what string } 114 115 func (err notSupportedError) Error() string { 116 return err.what + " not supported" 117 }