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