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. 6package filter 7 8import ( 9 "errors" 10 "regexp" 11 "strings" 12 13 "src.elv.sh/pkg/diag" 14 "src.elv.sh/pkg/parse" 15 "src.elv.sh/pkg/parse/cmpd" 16) 17 18// Compile parses and compiles a filter. 19func Compile(q string) (Filter, error) { 20 qn, errParse := parseFilter(q) 21 filter, errCompile := compileFilter(qn) 22 return filter, diag.Errors(errParse, errCompile) 23} 24 25func 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 31func 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 42func 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 54func 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 68var errEmptySubfilter = errors.New("empty subfilter") 69 70func 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 113type notSupportedError struct{ what string } 114 115func (err notSupportedError) Error() string { 116 return err.what + " not supported" 117} 118