github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/expr/expr.go (about) 1 // Copyright 2017 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Package expr provides support for evaluating boolean expressions. 6 package expr 7 8 import ( 9 "fmt" 10 "go/ast" 11 "go/parser" 12 "go/token" 13 "regexp" 14 "strconv" 15 "strings" 16 ) 17 18 // Expr holds a parsed boolean expression that matches some combination of attributes. 19 // 20 // Expressions are supplied as strings consisting of the following tokens: 21 // 22 // - Attributes, either as bare identifiers (only if compliant with 23 // https://golang.org/ref/spec#Identifiers) or as double-quoted strings 24 // (in which '*' characters are interpreted as wildcards) 25 // - Binary operators: && (and), || (or) 26 // - Unary operator: ! (not) 27 // - Grouping: (, ) 28 // 29 // The expression syntax is conveniently a subset of Go's syntax, so Go's parser and ast 30 // packages are used to convert the initial expression into a binary expression tree. 31 // 32 // After an Expr object is created from a string expression, it can be asked if 33 // it is satisfied by a supplied set of attributes. 34 type Expr struct { 35 root ast.Expr 36 } 37 38 // exprValidator is used to validate that a parsed Go expression is a 39 // valid boolean expression. It implements the ast.Visitor interface. 40 type exprValidator struct { 41 err error 42 } 43 44 // setErr stores a formatted error in ev's err field (if not already set) and 45 // returns nil to instruct ast.Walk to stop walking the expression. 46 func (ev *exprValidator) setErr(format string, args ...interface{}) ast.Visitor { 47 if ev.err == nil { 48 ev.err = fmt.Errorf(format, args...) 49 } 50 return nil 51 } 52 53 // Visit returns itself if n is a valid node in a boolean expression or sets 54 // ev's err field and returns nil if it isn't. 55 func (ev *exprValidator) Visit(n ast.Node) ast.Visitor { 56 // ast.Walk calls Visit(nil) after visiting non-nil children. 57 if n == nil { 58 return nil 59 } 60 61 switch v := n.(type) { 62 case *ast.BinaryExpr: 63 if v.Op != token.LAND && v.Op != token.LOR { 64 return ev.setErr("invalid binary operator %q", v.Op) 65 } 66 case *ast.ParenExpr: 67 case *ast.UnaryExpr: 68 if v.Op != token.NOT { 69 return ev.setErr("invalid unary operator %q", v.Op) 70 } 71 case *ast.Ident: 72 case *ast.BasicLit: 73 if v.Kind != token.STRING { 74 return ev.setErr("non-string literal %q", v.Value) 75 } 76 default: 77 return ev.setErr("invalid node of type %T", v) 78 } 79 return ev 80 } 81 82 // New parses and validates boolean expression s, returning an Expr object 83 // that can be used to test whether the expression is satisfied by different 84 // sets of attributes. 85 func New(s string) (*Expr, error) { 86 root, err := parser.ParseExpr(s) 87 if err != nil { 88 return nil, err 89 } 90 91 v := exprValidator{} 92 ast.Walk(&v, root) 93 return &Expr{root}, v.err 94 } 95 96 // Matches returns true if the expression is satisfied by attributes attrs. 97 func (e *Expr) Matches(attrs []string) bool { 98 am := make(map[string]struct{}, len(attrs)) 99 for _, a := range attrs { 100 am[a] = struct{}{} 101 } 102 return exprTrue(e.root, am) 103 } 104 105 // exprTrue returns true if e is satisfied by attributes attrs. 106 func exprTrue(e ast.Expr, attrs map[string]struct{}) bool { 107 switch v := e.(type) { 108 case *ast.BinaryExpr: 109 switch v.Op { 110 case token.LAND: 111 return exprTrue(v.X, attrs) && exprTrue(v.Y, attrs) 112 case token.LOR: 113 return exprTrue(v.X, attrs) || exprTrue(v.Y, attrs) 114 } 115 case *ast.ParenExpr: 116 return exprTrue(v.X, attrs) 117 case *ast.UnaryExpr: 118 switch v.Op { 119 case token.NOT: 120 return !exprTrue(v.X, attrs) 121 } 122 case *ast.Ident: 123 return hasAttr(attrs, v.Name) 124 case *ast.BasicLit: 125 switch v.Kind { 126 case token.STRING: 127 // Strip doublequotes. 128 str, err := strconv.Unquote(v.Value) 129 if err != nil { 130 return false 131 } 132 return hasAttr(attrs, str) 133 } 134 } 135 return false 136 } 137 138 func hasAttr(attrs map[string]struct{}, want string) bool { 139 if !strings.Contains(want, "*") { 140 _, ok := attrs[want] 141 return ok 142 } 143 144 // The pattern looks like a glob. Temporarily replace asterisks with zero bytes 145 // so we can escape other chars that have special meanings in regular expressions. 146 want = strings.Replace(want, "*", "\000", -1) 147 want = regexp.QuoteMeta(want) 148 want = strings.Replace(want, "\000", ".*", -1) 149 want = "^" + want + "$" 150 151 re, err := regexp.Compile(want) 152 if err != nil { 153 return false 154 } 155 for attr := range attrs { 156 if re.MatchString(attr) { 157 return true 158 } 159 } 160 return false 161 }