github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/eval_env.go (about) 1 // Copyright 2011 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package nin 16 17 import ( 18 "fmt" 19 "sort" 20 ) 21 22 // Env is an interface for a scope for variable (e.g. "$foo") lookups. 23 type Env interface { 24 LookupVariable(v string) string 25 } 26 27 // EvalStringToken is one token in the EvalString list of items. 28 type EvalStringToken struct { 29 Value string 30 IsSpecial bool 31 } 32 33 func (t *EvalStringToken) String() string { 34 out := fmt.Sprintf("%q:", t.Value) 35 if t.IsSpecial { 36 out += "raw" 37 } else { 38 out += "special" 39 } 40 return out 41 } 42 43 // EvalString is a tokenized string that contains variable references. 44 // 45 // Can be evaluated relative to an Env. 46 type EvalString struct { 47 Parsed []EvalStringToken 48 } 49 50 func (e *EvalString) String() string { 51 out := "" 52 for i, t := range e.Parsed { 53 if i != 0 { 54 out += "," 55 } 56 out += t.String() 57 } 58 return out 59 } 60 61 // Evaluate returns the evaluated string with variable expanded using value 62 // found in environment env. 63 func (e *EvalString) Evaluate(env Env) string { 64 // Warning: this function is recursive. 65 var z [64]string 66 var s []string 67 if l := len(e.Parsed); l <= cap(z) { 68 s = z[:l] 69 } else { 70 s = make([]string, l) 71 } 72 total := 0 73 for i, p := range e.Parsed { 74 if !p.IsSpecial { 75 x := p.Value 76 s[i] = x 77 total += len(x) 78 } else { 79 // TODO(maruel): What about when the variable is undefined? It'd be good 80 // to surface this to the user, at least optionally. 81 x := env.LookupVariable(p.Value) 82 s[i] = x 83 total += len(x) 84 } 85 } 86 out := make([]byte, total) 87 offset := 0 88 for _, x := range s { 89 l := len(x) 90 copy(out[offset:], x) 91 offset += l 92 } 93 return unsafeString(out) 94 } 95 96 // Serialize constructs a human-readable representation of the parsed state. 97 // 98 // Used in tests. 99 func (e *EvalString) Serialize() string { 100 result := "" 101 for _, i := range e.Parsed { 102 result += "[" 103 if i.IsSpecial { 104 result += "$" 105 } 106 result += i.Value 107 result += "]" 108 } 109 return result 110 } 111 112 // Unparse returns the string with variables not expanded. 113 // 114 // Used for diagnostics. 115 func (e *EvalString) Unparse() string { 116 result := "" 117 for _, i := range e.Parsed { 118 special := i.IsSpecial 119 if special { 120 result += "${" 121 } 122 result += i.Value 123 if special { 124 result += "}" 125 } 126 } 127 return result 128 } 129 130 // 131 132 // IsReservedBinding returns true if the binding name is reserved by ninja. 133 func IsReservedBinding(v string) bool { 134 return v == "command" || 135 v == "depfile" || 136 v == "dyndep" || 137 v == "description" || 138 v == "deps" || 139 v == "generator" || 140 v == "pool" || 141 v == "restat" || 142 v == "rspfile" || 143 v == "rspfile_content" || 144 v == "msvc_deps_prefix" 145 } 146 147 // Rule is an invocable build command and associated metadata (description, 148 // etc.). 149 type Rule struct { 150 Name string 151 Bindings map[string]*EvalString 152 } 153 154 // NewRule returns an initialized Rule. 155 func NewRule(name string) *Rule { 156 return &Rule{ 157 Name: name, 158 Bindings: map[string]*EvalString{}, 159 } 160 } 161 162 func (r *Rule) String() string { 163 out := "Rule:" + r.Name + "{" 164 names := make([]string, 0, len(r.Bindings)) 165 for n := range r.Bindings { 166 names = append(names, n) 167 } 168 sort.Strings(names) 169 for i, n := range names { 170 if i != 0 { 171 out += "," 172 } 173 out += n + ":" + r.Bindings[n].String() 174 } 175 out += "}" 176 return out 177 } 178 179 // 180 181 // BindingEnv is an Env which contains a mapping of variables to values 182 // as well as a pointer to a parent scope. 183 type BindingEnv struct { 184 Bindings map[string]string 185 Rules map[string]*Rule 186 Parent *BindingEnv 187 } 188 189 // NewBindingEnv returns an initialized BindingEnv. 190 func NewBindingEnv(parent *BindingEnv) *BindingEnv { 191 return &BindingEnv{ 192 Bindings: map[string]string{}, 193 Rules: map[string]*Rule{}, 194 Parent: parent, 195 } 196 } 197 198 // String serializes the bindings. 199 func (b *BindingEnv) String() string { 200 out := "BindingEnv{" 201 if b.Parent != nil { 202 out += "(has parent)" 203 } 204 out += "\n Bindings:" 205 names := make([]string, 0, len(b.Bindings)) 206 for n := range b.Bindings { 207 names = append(names, n) 208 } 209 sort.Strings(names) 210 for _, n := range names { 211 out += "\n " + n + ":" + b.Bindings[n] 212 } 213 out += "\n Rules:" 214 names = make([]string, 0, len(b.Rules)) 215 for n := range b.Rules { 216 names = append(names, n) 217 } 218 sort.Strings(names) 219 for _, n := range names { 220 out += "\n " + n + ":" + b.Rules[n].String() 221 } 222 out += "\n}" 223 return out 224 } 225 226 // LookupVariable returns a variable's value. 227 func (b *BindingEnv) LookupVariable(v string) string { 228 if i, ok := b.Bindings[v]; ok { 229 return i 230 } 231 if b.Parent != nil { 232 return b.Parent.LookupVariable(v) 233 } 234 return "" 235 } 236 237 // LookupRule returns a rule by name. 238 func (b *BindingEnv) LookupRule(ruleName string) *Rule { 239 if i := b.Rules[ruleName]; i != nil { 240 return i 241 } 242 if b.Parent != nil { 243 return b.Parent.LookupRule(ruleName) 244 } 245 return nil 246 } 247 248 // This is tricky. Edges want lookup scope to go in this order: 249 // 1) value set on edge itself (edge->env) 250 // 2) value set on rule, with expansion in the edge's scope 251 // 3) value set on enclosing scope of edge (edge->env->parent) 252 // This function takes as parameters the necessary info to do (2). 253 func (b *BindingEnv) lookupWithFallback(v string, eval *EvalString, env Env) string { 254 if i, ok := b.Bindings[v]; ok { 255 return i 256 } 257 if eval != nil { 258 return eval.Evaluate(env) 259 } 260 if b.Parent != nil { 261 return b.Parent.LookupVariable(v) 262 } 263 return "" 264 }