Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Android/build/build/blueprint/   (Android Betriebssystem Version 17©)  Datei vom 26.5.2026 mit Größe 13 kB image not shown  

Quelle  ninja_strings.go   Sprache: unbekannt

 
Spracherkennung für: .go vermutete Sprache: Unknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]

// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package blueprint

import (
 "bytes"
 "fmt"
 "io"
 "slices"
 "strings"
)

const eof = -1

var (
 defaultEscaper = strings.NewReplacer(
  "\n", "$\n")
 inputEscaper = strings.NewReplacer(
  "\n", "$\n",
  " ", "$ ")
 outputEscaper = strings.NewReplacer(
  "\n", "$\n",
  " ", "$ ",
  ":", "$:")
)

// ninjaString contains the parsed result of a string that can contain references to variables (e.g. $cflags) that will
// be propagated to the build.ninja file.  For literal strings with no variable references, the variables field will be
// nil. For strings with variable references str contains the original, unparsed string, and variables contains a
// pointer to a list of references, each with a span of bytes they should replace and a Variable interface.
type ninjaString struct {
 str       string
 variables *[]variableReference
}

// variableReference contains information about a single reference to a variable (e.g. $cflags) inside a parsed
// ninjaString.  start and end are int32 to reduce memory usage.  A nil variable is a special case of an inserted '$'
// at the beginning of the string to handle leading whitespace that must not be stripped by ninja.
type variableReference struct {
 // start is the offset of the '$' character from the beginning of the unparsed string.
 start int32

 // end is the offset of the character _after_ the final character of the variable name (or '}' if using the
 //'${}'  syntax)
 end int32

 variable Variable
}

type scope interface {
 LookupVariable(name string) (Variable, error)
 IsRuleVisible(rule Rule) bool
 IsPoolVisible(pool Pool) bool
}

func simpleNinjaString(str string) *ninjaString {
 return &ninjaString{str: str}
}

type parseState struct {
 scope        scope
 str          string
 varStart     int
 varNameStart int
 result       *ninjaString
}

func (ps *parseState) pushVariable(start, end int, v Variable) {
 if ps.result.variables == nil {
  ps.result.variables = &[]variableReference{{start: int32(start), end: int32(end), variable: v}}
 } else {
  *ps.result.variables = append(*ps.result.variables, variableReference{start: int32(start), end: int32(end), variable: v})
 }
}

type stateFunc func(*parseState, int, rune) (stateFunc, error)

// parseNinjaString parses an unescaped ninja string (i.e. all $<something>
// occurrences are expected to be variables or $$) and returns a *ninjaString
// that contains the original string and a list of the referenced variables.
func parseNinjaString(scope scope, str string) (*ninjaString, error) {
 ninjaString, str, err := parseNinjaOrSimpleString(scope, str)
 if err != nil {
  return nil, err
 }
 if ninjaString != nil {
  return ninjaString, nil
 }
 return simpleNinjaString(str), nil
}

// parseNinjaOrSimpleString parses an unescaped ninja string (i.e. all $<something>
// occurrences are expected to be variables or $$) and returns either a *ninjaString
// if the string contains ninja variable references, or the original string and nil
// for the *ninjaString if it doesn't.
func parseNinjaOrSimpleString(scope scope, str string) (*ninjaString, string, error) {
 // naively pre-allocate slice by counting $ signs
 n := strings.Count(str, "$")
 if n == 0 {
  if len(str) > 0 && str[0] == ' ' {
   str = "$" + str
  }
  return nil, str, nil
 }
 variableReferences := make([]variableReference, 0, n)
 result := &ninjaString{
  str:       str,
  variables: &variableReferences,
 }

 parseState := &parseState{
  scope:  scope,
  str:    str,
  result: result,
 }

 state := parseFirstRuneState
 var err error
 for i := 0; i < len(str); i++ {
  r := rune(str[i])
  state, err = state(parseState, i, r)
  if err != nil {
   return nil, "", fmt.Errorf("error parsing ninja string %q: %s", str, err)
  }
 }

 _, err = state(parseState, len(parseState.str), eof)
 if err != nil {
  return nil, "", err
 }

 // All the '$' characters counted initially could have been "$$" escapes, leaving no
 // variable references.  Deallocate the variables slice if so.
 if len(*result.variables) == 0 {
  result.variables = nil
 }

 return result, "", nil
}

func parseFirstRuneState(state *parseState, i int, r rune) (stateFunc, error) {
 if r == ' ' {
  state.pushVariable(01, nil)
 }
 return parseStringState(state, i, r)
}

func parseStringState(state *parseState, i int, r rune) (stateFunc, error) {
 switch {
 case r == '$':
  state.varStart = i
  return parseDollarStartState, nil

 case r == eof:
  return nil, nil

 default:
  return parseStringState, nil
 }
}

func parseDollarStartState(state *parseState, i int, r rune) (stateFunc, error) {
 switch {
 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
  r >= '0' && r <= '9', r == '_', r == '-':
  // The beginning of a of the variable name.
  state.varNameStart = i
  return parseDollarState, nil

 case r == '$':
  // Just a "$$".  Go back to parseStringState.
  return parseStringState, nil

 case r == '{':
  // This is a bracketted variable name (e.g. "${blah.blah}").
  state.varNameStart = i + 1
  return parseBracketsState, nil

 case r == eof:
  return nil, fmt.Errorf("unexpected end of string after '$'")

 default:
  // This was some arbitrary character following a dollar sign,
  // which is not allowed.
  return nil, fmt.Errorf("invalid character after '$' at byte "+
   "offset %d", i)
 }
}

func parseDollarState(state *parseState, i int, r rune) (stateFunc, error) {
 switch {
 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
  r >= '0' && r <= '9', r == '_', r == '-':
  // A part of the variable name.  Keep going.
  return parseDollarState, nil
 }

 // The variable name has ended, output what we have.
 v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
 if err != nil {
  return nil, err
 }

 state.pushVariable(state.varStart, i, v)

 switch {
 case r == '$':
  // A dollar after the variable name (e.g. "$blah$").  Start a new one.
  state.varStart = i
  return parseDollarStartState, nil

 case r == eof:
  return nil, nil

 default:
  return parseStringState, nil
 }
}

func parseBracketsState(state *parseState, i int, r rune) (stateFunc, error) {
 switch {
 case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
  r >= '0' && r <= '9', r == '_', r == '-', r == '.':
  // A part of the variable name.  Keep going.
  return parseBracketsState, nil

 case r == '}':
  if state.varNameStart == i {
   // The brackets were immediately closed.  That's no good.
   return nil, fmt.Errorf("empty variable name at byte offset %d",
    i)
  }

  // This is the end of the variable name.
  v, err := state.scope.LookupVariable(state.str[state.varNameStart:i])
  if err != nil {
   return nil, err
  }

  state.pushVariable(state.varStart, i+1, v)
  return parseStringState, nil

 case r == eof:
  return nil, fmt.Errorf("unexpected end of string in variable name")

 default:
  // This character isn't allowed in a variable name.
  return nil, fmt.Errorf("invalid character in variable name at "+
   "byte offset %d", i)
 }
}

// parseNinjaStrings converts a list of strings to *ninjaStrings by finding the references
// to ninja variables contained in the strings.
func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
 error) {

 if len(strs) == 0 {
  return nil, nil
 }
 result := make([]*ninjaString, len(strs))
 for i, str := range strs {
  ninjaStr, err := parseNinjaString(scope, str)
  if err != nil {
   return nil, fmt.Errorf("error parsing element %d: %s", i, err)
  }
  result[i] = ninjaStr
 }
 return result, nil
}

// parseNinjaOrSimpleStrings splits a list of strings into *ninjaStrings if they have ninja
// variable references or a list of strings if they don't.  If none of the input strings contain
// ninja variable references (a very common case) then it returns the unmodified input slice as
// the output slice.
func parseNinjaOrSimpleStrings(scope scope, strs []string) ([]*ninjaString, []string, error) {
 if len(strs) == 0 {
  return nil, strs, nil
 }

 // allSimpleStrings is true until the first time a string with ninja variable references is found.
 allSimpleStrings := true
 var simpleStrings []string
 var ninjaStrings []*ninjaString

 for i, str := range strs {
  ninjaStr, simpleStr, err := parseNinjaOrSimpleString(scope, str)
  if err != nil {
   return nil, nil, fmt.Errorf("error parsing element %d: %s", i, err)
  } else if ninjaStr != nil {
   ninjaStrings = append(ninjaStrings, ninjaStr)
   if allSimpleStrings && i > 0 {
    // If all previous strings had no ninja variable references then they weren't copied into
    // simpleStrings to avoid allocating it if the input slice is reused as the output.  Allocate
    // simpleStrings and copy all the previous strings into it.
    simpleStrings = make([]string, i, len(strs))
    copy(simpleStrings, strs[:i])
   }
   allSimpleStrings = false
  } else {
   if !allSimpleStrings {
    // Only copy into the output slice if at least one string with ninja variable references
    // was found.  Skipped strings will be copied the first time a string with ninja variable
    // is found.
    simpleStrings = append(simpleStrings, simpleStr)
   }
  }
 }
 if allSimpleStrings {
  // None of the input strings had ninja variable references, return the input slice as the output.
  return nil, strs, nil
 }
 return ninjaStrings, simpleStrings, nil
}

func (n *ninjaString) Value(nameTracker *nameTracker) string {
 if n.variables == nil || len(*n.variables) == 0 {
  return defaultEscaper.Replace(n.str)
 }
 str := &strings.Builder{}
 n.ValueWithEscaper(str, nameTracker, defaultEscaper)
 return str.String()
}

func (n *ninjaString) ValueWithEscaper(w io.StringWriter, nameTracker *nameTracker, escaper *strings.Replacer) {

 if n.variables == nil || len(*n.variables) == 0 {
  w.WriteString(escaper.Replace(n.str))
  return
 }

 i := 0
 for _, v := range *n.variables {
  w.WriteString(escaper.Replace(n.str[i:v.start]))
  if v.variable == nil {
   w.WriteString("$ ")
  } else {
   w.WriteString("${")
   w.WriteString(nameTracker.Variable(v.variable))
   w.WriteString("}")
  }
  i = int(v.end)
 }
 w.WriteString(escaper.Replace(n.str[i:len(n.str)]))
}

func (n *ninjaString) Eval(variables map[Variable]*ninjaString) (string, error) {
 if n.variables == nil || len(*n.variables) == 0 {
  return n.str, nil
 }

 w := &strings.Builder{}
 i := 0
 for _, v := range *n.variables {
  w.WriteString(n.str[i:v.start])
  if v.variable == nil {
   w.WriteString(" ")
  } else {
   variable, ok := variables[v.variable]
   if !ok {
    return "", fmt.Errorf("no such global variable: %s", v.variable)
   }
   value, err := variable.Eval(variables)
   if err != nil {
    return "", err
   }
   w.WriteString(value)
  }
  i = int(v.end)
 }
 w.WriteString(n.str[i:len(n.str)])
 return w.String(), nil
}

func (n *ninjaString) Variables() []Variable {
 if n.variables == nil || len(*n.variables) == 0 {
  return nil
 }

 variables := make([]Variable, 0, len(*n.variables))
 for _, v := range *n.variables {
  if v.variable != nil {
   variables = append(variables, v.variable)
  }
 }
 return variables
}

func validateNinjaName(name string) error {
 for i, r := range name {
  valid := (r >= 'a' && r <= 'z') ||
   (r >= 'A' && r <= 'Z') ||
   (r >= '0' && r <= '9') ||
   (r == '_') ||
   (r == '-') ||
   (r == '.')
  if !valid {

   return fmt.Errorf("%q contains an invalid Ninja name character "+
    "%q at byte offset %d", name, r, i)
  }
 }
 return nil
}

func toNinjaName(name string) string {
 ret := bytes.Buffer{}
 ret.Grow(len(name))
 for _, r := range name {
  valid := (r >= 'a' && r <= 'z') ||
   (r >= 'A' && r <= 'Z') ||
   (r >= '0' && r <= '9') ||
   (r == '_') ||
   (r == '-') ||
   (r == '.')
  if valid {
   ret.WriteRune(r)
  } else {
   // TODO(jeffrygaston): do escaping so that toNinjaName won't ever output duplicate
   // names for two different input names
   ret.WriteRune('_')
  }
 }

 return ret.String()
}

var builtinRuleArgs = []string{"out", "in", "in_newline"}

func validateArgName(argName string) error {
 err := validateNinjaName(argName)
 if err != nil {
  return err
 }

 // We only allow globals within the rule's package to be used as rule
 // arguments.  A global in another package can always be mirrored into
 // the rule's package by defining a new variable, so this doesn't limit
 // what's possible.  This limitation prevents situations where a Build
 // invocation in another package must use the rule-defining package's
 // import name for a 3rd package in order to set the rule's arguments.
 if strings.ContainsRune(argName, '.') {
  return fmt.Errorf("%q contains a '.' character", argName)
 }

 if argName == "tags" {
  return fmt.Errorf("\"tags\" is a reserved argument name")
 }

 for _, builtin := range builtinRuleArgs {
  if argName == builtin {
   return fmt.Errorf("%q conflicts with Ninja built-in", argName)
  }
 }

 return nil
}

func validateArgNames(argNames []string) error {
 for _, argName := range argNames {
  err := validateArgName(argName)
  if err != nil {
   return err
  }
 }

 return nil
}

func ninjaStringsEqual(a, b *ninjaString) bool {
 return a.str == b.str &&
  (a.variables == nil) == (b.variables == nil) &&
  (a.variables == nil ||
   slices.Equal(*a.variables, *b.variables))
}

[zur Elbe Produktseite wechseln0.20QuellennavigatorsAnalyse erneut starten2026-06-28]