package filter
import (
"errors"
"regexp"
"strings"
"src.elv.sh/pkg/diag"
"src.elv.sh/pkg/parse"
"src.elv.sh/pkg/parse/cmpd"
)
func Compile (q string ) (Filter , error ) {
qn , errParse := parseFilter (q )
filter , errCompile := compileFilter (qn )
return filter , diag .Errors (errParse , errCompile )
}
func parseFilter (q string ) (*parse .Filter , error ) {
qn := &parse .Filter {}
err := parse .ParseAs (parse .Source {Name : "filter" , Code : q }, qn , parse .Config {})
return qn , err
}
func compileFilter (qn *parse .Filter ) (Filter , error ) {
if len (qn .Opts ) > 0 {
return nil , notSupportedError {"option" }
}
qs , err := compileCompounds (qn .Args )
if err != nil {
return nil , err
}
return andFilter {qs }, nil
}
func compileCompounds (ns []*parse .Compound ) ([]Filter , error ) {
qs := make ([]Filter , len (ns ))
for i , n := range ns {
q , err := compileCompound (n )
if err != nil {
return nil , err
}
qs [i ] = q
}
return qs , nil
}
func compileCompound (n *parse .Compound ) (Filter , error ) {
if pn , ok := cmpd .Primary (n ); ok {
switch pn .Type {
case parse .Bareword , parse .SingleQuoted , parse .DoubleQuoted :
s := pn .Value
ignoreCase := s == strings .ToLower (s )
return substringFilter {s , ignoreCase }, nil
case parse .List :
return compileList (pn .Elements )
}
}
return nil , notSupportedError {cmpd .Shape (n )}
}
var errEmptySubfilter = errors .New ("empty subfilter" )
func compileList (elems []*parse .Compound ) (Filter , error ) {
if len (elems ) == 0 {
return nil , errEmptySubfilter
}
head , ok := cmpd .StringLiteral (elems [0 ])
if !ok {
return nil , notSupportedError {"non-literal subfilter head" }
}
switch head {
case "re" :
if len (elems ) == 1 {
return nil , notSupportedError {"re subfilter with no argument" }
}
if len (elems ) > 2 {
return nil , notSupportedError {"re subfilter with two or more arguments" }
}
arg := elems [1 ]
s , ok := cmpd .StringLiteral (arg )
if !ok {
return nil , notSupportedError {"re subfilter with " + cmpd .Shape (arg )}
}
p , err := regexp .Compile (s )
if err != nil {
return nil , err
}
return regexpFilter {p }, nil
case "and" :
qs , err := compileCompounds (elems [1 :])
if err != nil {
return nil , err
}
return andFilter {qs }, nil
case "or" :
qs , err := compileCompounds (elems [1 :])
if err != nil {
return nil , err
}
return orFilter {qs }, nil
default :
return nil , notSupportedError {"head " + parse .SourceText (elems [0 ])}
}
}
type notSupportedError struct { what string }
func (err notSupportedError ) Error () string {
return err .what + " not supported"
}