github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/ninja_strings.go (about)

     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  }