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