1// Copyright 2014 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 15package blueprint 16 17import ( 18 "bytes" 19 "fmt" 20 "strings" 21) 22 23const eof = -1 24 25var ( 26 defaultEscaper = strings.NewReplacer( 27 "\n", "$\n") 28 inputEscaper = strings.NewReplacer( 29 "\n", "$\n", 30 " ", "$ ") 31 outputEscaper = strings.NewReplacer( 32 "\n", "$\n", 33 " ", "$ ", 34 ":", "$:") 35) 36 37type ninjaString interface { 38 Value(pkgNames map[*packageContext]string) string 39 ValueWithEscaper(pkgNames map[*packageContext]string, escaper *strings.Replacer) string 40 Eval(variables map[Variable]ninjaString) (string, error) 41 Variables() []Variable 42} 43 44type varNinjaString struct { 45 strings []string 46 variables []Variable 47} 48 49type literalNinjaString string 50 51type scope interface { 52 LookupVariable(name string) (Variable, error) 53 IsRuleVisible(rule Rule) bool 54 IsPoolVisible(pool Pool) bool 55} 56 57func simpleNinjaString(str string) ninjaString { 58 return literalNinjaString(str) 59} 60 61type parseState struct { 62 scope scope 63 str string 64 pendingStr string 65 stringStart int 66 varStart int 67 result *varNinjaString 68} 69 70func (ps *parseState) pushVariable(v Variable) { 71 if len(ps.result.variables) == len(ps.result.strings) { 72 // Last push was a variable, we need a blank string separator 73 ps.result.strings = append(ps.result.strings, "") 74 } 75 if ps.pendingStr != "" { 76 panic("oops, pushed variable with pending string") 77 } 78 ps.result.variables = append(ps.result.variables, v) 79} 80 81func (ps *parseState) pushString(s string) { 82 if len(ps.result.strings) != len(ps.result.variables) { 83 panic("oops, pushed string after string") 84 } 85 ps.result.strings = append(ps.result.strings, ps.pendingStr+s) 86 ps.pendingStr = "" 87} 88 89type stateFunc func(*parseState, int, rune) (stateFunc, error) 90 91// parseNinjaString parses an unescaped ninja string (i.e. all $<something> 92// occurrences are expected to be variables or $$) and returns a list of the 93// variable names that the string references. 94func parseNinjaString(scope scope, str string) (ninjaString, error) { 95 // naively pre-allocate slices by counting $ signs 96 n := strings.Count(str, "$") 97 if n == 0 { 98 if strings.HasPrefix(str, " ") { 99 str = "$" + str 100 } 101 return literalNinjaString(str), nil 102 } 103 result := &varNinjaString{ 104 strings: make([]string, 0, n+1), 105 variables: make([]Variable, 0, n), 106 } 107 108 parseState := &parseState{ 109 scope: scope, 110 str: str, 111 result: result, 112 } 113 114 state := parseFirstRuneState 115 var err error 116 for i := 0; i < len(str); i++ { 117 r := rune(str[i]) 118 state, err = state(parseState, i, r) 119 if err != nil { 120 return nil, err 121 } 122 } 123 124 _, err = state(parseState, len(parseState.str), eof) 125 if err != nil { 126 return nil, err 127 } 128 129 return result, nil 130} 131 132func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) { 133 if r == ' ' { 134 state.pendingStr += "$" 135 } 136 return parseStringState(state, i, r) 137} 138 139func parseStringState(state *parseState, i int, r rune) (stateFunc, error) { 140 switch { 141 case r == '$': 142 state.varStart = i + 1 143 return parseDollarStartState, nil 144 145 case r == eof: 146 state.pushString(state.str[state.stringStart:i]) 147 return nil, nil 148 149 default: 150 return parseStringState, nil 151 } 152} 153 154func parseDollarStartState(state *parseState, i int, r rune) (stateFunc, error) { 155 switch { 156 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', 157 r >= '0' && r <= '9', r == '_', r == '-': 158 // The beginning of a of the variable name. Output the string and 159 // keep going. 160 state.pushString(state.str[state.stringStart : i-1]) 161 return parseDollarState, nil 162 163 case r == '$': 164 // Just a "$$". Go back to parseStringState without changing 165 // state.stringStart. 166 return parseStringState, nil 167 168 case r == '{': 169 // This is a bracketted variable name (e.g. "${blah.blah}"). Output 170 // the string and keep going. 171 state.pushString(state.str[state.stringStart : i-1]) 172 state.varStart = i + 1 173 return parseBracketsState, nil 174 175 case r == eof: 176 return nil, fmt.Errorf("unexpected end of string after '$'") 177 178 default: 179 // This was some arbitrary character following a dollar sign, 180 // which is not allowed. 181 return nil, fmt.Errorf("invalid character after '$' at byte "+ 182 "offset %d", i) 183 } 184} 185 186func parseDollarState(state *parseState, i int, r rune) (stateFunc, error) { 187 switch { 188 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', 189 r >= '0' && r <= '9', r == '_', r == '-': 190 // A part of the variable name. Keep going. 191 return parseDollarState, nil 192 193 case r == '$': 194 // A dollar after the variable name (e.g. "$blah$"). Output the 195 // variable we have and start a new one. 196 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 197 if err != nil { 198 return nil, err 199 } 200 201 state.pushVariable(v) 202 state.varStart = i + 1 203 state.stringStart = i 204 205 return parseDollarStartState, nil 206 207 case r == eof: 208 // This is the end of the variable name. 209 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 210 if err != nil { 211 return nil, err 212 } 213 214 state.pushVariable(v) 215 216 // We always end with a string, even if it's an empty one. 217 state.pushString("") 218 219 return nil, nil 220 221 default: 222 // We've just gone past the end of the variable name, so record what 223 // we have. 224 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 225 if err != nil { 226 return nil, err 227 } 228 229 state.pushVariable(v) 230 state.stringStart = i 231 return parseStringState, nil 232 } 233} 234 235func parseBracketsState(state *parseState, i int, r rune) (stateFunc, error) { 236 switch { 237 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', 238 r >= '0' && r <= '9', r == '_', r == '-', r == '.': 239 // A part of the variable name. Keep going. 240 return parseBracketsState, nil 241 242 case r == '}': 243 if state.varStart == i { 244 // The brackets were immediately closed. That's no good. 245 return nil, fmt.Errorf("empty variable name at byte offset %d", 246 i) 247 } 248 249 // This is the end of the variable name. 250 v, err := state.scope.LookupVariable(state.str[state.varStart:i]) 251 if err != nil { 252 return nil, err 253 } 254 255 state.pushVariable(v) 256 state.stringStart = i + 1 257 return parseStringState, nil 258 259 case r == eof: 260 return nil, fmt.Errorf("unexpected end of string in variable name") 261 262 default: 263 // This character isn't allowed in a variable name. 264 return nil, fmt.Errorf("invalid character in variable name at "+ 265 "byte offset %d", i) 266 } 267} 268 269func parseNinjaStrings(scope scope, strs []string) ([]ninjaString, 270 error) { 271 272 if len(strs) == 0 { 273 return nil, nil 274 } 275 result := make([]ninjaString, len(strs)) 276 for i, str := range strs { 277 ninjaStr, err := parseNinjaString(scope, str) 278 if err != nil { 279 return nil, fmt.Errorf("error parsing element %d: %s", i, err) 280 } 281 result[i] = ninjaStr 282 } 283 return result, nil 284} 285 286func (n varNinjaString) Value(pkgNames map[*packageContext]string) string { 287 return n.ValueWithEscaper(pkgNames, defaultEscaper) 288} 289 290func (n varNinjaString) ValueWithEscaper(pkgNames map[*packageContext]string, 291 escaper *strings.Replacer) string { 292 293 if len(n.strings) == 1 { 294 return escaper.Replace(n.strings[0]) 295 } 296 297 str := strings.Builder{} 298 str.WriteString(escaper.Replace(n.strings[0])) 299 for i, v := range n.variables { 300 str.WriteString("${") 301 str.WriteString(v.fullName(pkgNames)) 302 str.WriteString("}") 303 str.WriteString(escaper.Replace(n.strings[i+1])) 304 } 305 306 return str.String() 307} 308 309func (n varNinjaString) Eval(variables map[Variable]ninjaString) (string, error) { 310 str := n.strings[0] 311 for i, v := range n.variables { 312 variable, ok := variables[v] 313 if !ok { 314 return "", fmt.Errorf("no such global variable: %s", v) 315 } 316 value, err := variable.Eval(variables) 317 if err != nil { 318 return "", err 319 } 320 str += value + n.strings[i+1] 321 } 322 return str, nil 323} 324 325func (n varNinjaString) Variables() []Variable { 326 return n.variables 327} 328 329func (l literalNinjaString) Value(pkgNames map[*packageContext]string) string { 330 return l.ValueWithEscaper(pkgNames, defaultEscaper) 331} 332 333func (l literalNinjaString) ValueWithEscaper(pkgNames map[*packageContext]string, 334 escaper *strings.Replacer) string { 335 return escaper.Replace(string(l)) 336} 337 338func (l literalNinjaString) Eval(variables map[Variable]ninjaString) (string, error) { 339 return string(l), nil 340} 341 342func (l literalNinjaString) Variables() []Variable { 343 return nil 344} 345 346func validateNinjaName(name string) error { 347 for i, r := range name { 348 valid := (r >= 'a' && r <= 'z') || 349 (r >= 'A' && r <= 'Z') || 350 (r >= '0' && r <= '9') || 351 (r == '_') || 352 (r == '-') || 353 (r == '.') 354 if !valid { 355 356 return fmt.Errorf("%q contains an invalid Ninja name character "+ 357 "%q at byte offset %d", name, r, i) 358 } 359 } 360 return nil 361} 362 363func toNinjaName(name string) string { 364 ret := bytes.Buffer{} 365 ret.Grow(len(name)) 366 for _, r := range name { 367 valid := (r >= 'a' && r <= 'z') || 368 (r >= 'A' && r <= 'Z') || 369 (r >= '0' && r <= '9') || 370 (r == '_') || 371 (r == '-') || 372 (r == '.') 373 if valid { 374 ret.WriteRune(r) 375 } else { 376 // TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate 377 // names for two different input names 378 ret.WriteRune('_') 379 } 380 } 381 382 return ret.String() 383} 384 385var builtinRuleArgs = []string{"out", "in"} 386 387func validateArgName(argName string) error { 388 err := validateNinjaName(argName) 389 if err != nil { 390 return err 391 } 392 393 // We only allow globals within the rule's package to be used as rule 394 // arguments. A global in another package can always be mirrored into 395 // the rule's package by defining a new variable, so this doesn't limit 396 // what's possible. This limitation prevents situations where a Build 397 // invocation in another package must use the rule-defining package's 398 // import name for a 3rd package in order to set the rule's arguments. 399 if strings.ContainsRune(argName, '.') { 400 return fmt.Errorf("%q contains a '.' character", argName) 401 } 402 403 for _, builtin := range builtinRuleArgs { 404 if argName == builtin { 405 return fmt.Errorf("%q conflicts with Ninja built-in", argName) 406 } 407 } 408 409 return nil 410} 411 412func validateArgNames(argNames []string) error { 413 for _, argName := range argNames { 414 err := validateArgName(argName) 415 if err != nil { 416 return err 417 } 418 } 419 420 return nil 421} 422