Home | History | Annotate | Download | only in blueprint
      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 
     15 package blueprint
     16 
     17 import (
     18 	"bytes"
     19 	"fmt"
     20 	"strings"
     21 )
     22 
     23 const eof = -1
     24 
     25 var (
     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 
     37 type ninjaString struct {
     38 	strings   []string
     39 	variables []Variable
     40 }
     41 
     42 type scope interface {
     43 	LookupVariable(name string) (Variable, error)
     44 	IsRuleVisible(rule Rule) bool
     45 	IsPoolVisible(pool Pool) bool
     46 }
     47 
     48 func simpleNinjaString(str string) *ninjaString {
     49 	return &ninjaString{
     50 		strings: []string{str},
     51 	}
     52 }
     53 
     54 type parseState struct {
     55 	scope       scope
     56 	str         string
     57 	pendingStr  string
     58 	stringStart int
     59 	varStart    int
     60 	result      *ninjaString
     61 }
     62 
     63 func (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 
     74 func (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 
     82 type 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.
     87 func 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 
    119 func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) {
    120 	if r == ' ' {
    121 		state.pendingStr += "$"
    122 	}
    123 	return parseStringState(state, i, r)
    124 }
    125 
    126 func 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 
    141 func 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 
    173 func 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 
    222 func 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 
    256 func 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 
    273 func (n *ninjaString) Value(pkgNames map[*packageContext]string) string {
    274 	return n.ValueWithEscaper(pkgNames, defaultEscaper)
    275 }
    276 
    277 func (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 
    288 func (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 
    304 func 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 
    321 func 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 			// TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate
    335 			// names for two different input names
    336 			ret.WriteRune('_')
    337 		}
    338 	}
    339 
    340 	return ret.String()
    341 }
    342 
    343 var builtinRuleArgs = []string{"out", "in"}
    344 
    345 func validateArgName(argName string) error {
    346 	err := validateNinjaName(argName)
    347 	if err != nil {
    348 		return err
    349 	}
    350 
    351 	// We only allow globals within the rule's package to be used as rule
    352 	// arguments.  A global in another package can always be mirrored into
    353 	// the rule's package by defining a new variable, so this doesn't limit
    354 	// what's possible.  This limitation prevents situations where a Build
    355 	// invocation in another package must use the rule-defining package's
    356 	// import name for a 3rd package in order to set the rule's arguments.
    357 	if strings.ContainsRune(argName, '.') {
    358 		return fmt.Errorf("%q contains a '.' character", argName)
    359 	}
    360 
    361 	for _, builtin := range builtinRuleArgs {
    362 		if argName == builtin {
    363 			return fmt.Errorf("%q conflicts with Ninja built-in", argName)
    364 		}
    365 	}
    366 
    367 	return nil
    368 }
    369 
    370 func validateArgNames(argNames []string) error {
    371 	for _, argName := range argNames {
    372 		err := validateArgName(argName)
    373 		if err != nil {
    374 			return err
    375 		}
    376 	}
    377 
    378 	return nil
    379 }
    380