github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/eval_env.go (about)

     1  // Copyright 2011 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 nin
    16  
    17  import (
    18  	"fmt"
    19  	"sort"
    20  )
    21  
    22  // Env is an interface for a scope for variable (e.g. "$foo") lookups.
    23  type Env interface {
    24  	LookupVariable(v string) string
    25  }
    26  
    27  // EvalStringToken is one token in the EvalString list of items.
    28  type EvalStringToken struct {
    29  	Value     string
    30  	IsSpecial bool
    31  }
    32  
    33  func (t *EvalStringToken) String() string {
    34  	out := fmt.Sprintf("%q:", t.Value)
    35  	if t.IsSpecial {
    36  		out += "raw"
    37  	} else {
    38  		out += "special"
    39  	}
    40  	return out
    41  }
    42  
    43  // EvalString is a tokenized string that contains variable references.
    44  //
    45  // Can be evaluated relative to an Env.
    46  type EvalString struct {
    47  	Parsed []EvalStringToken
    48  }
    49  
    50  func (e *EvalString) String() string {
    51  	out := ""
    52  	for i, t := range e.Parsed {
    53  		if i != 0 {
    54  			out += ","
    55  		}
    56  		out += t.String()
    57  	}
    58  	return out
    59  }
    60  
    61  // Evaluate returns the evaluated string with variable expanded using value
    62  // found in environment env.
    63  func (e *EvalString) Evaluate(env Env) string {
    64  	// Warning: this function is recursive.
    65  	var z [64]string
    66  	var s []string
    67  	if l := len(e.Parsed); l <= cap(z) {
    68  		s = z[:l]
    69  	} else {
    70  		s = make([]string, l)
    71  	}
    72  	total := 0
    73  	for i, p := range e.Parsed {
    74  		if !p.IsSpecial {
    75  			x := p.Value
    76  			s[i] = x
    77  			total += len(x)
    78  		} else {
    79  			// TODO(maruel): What about when the variable is undefined? It'd be good
    80  			// to surface this to the user, at least optionally.
    81  			x := env.LookupVariable(p.Value)
    82  			s[i] = x
    83  			total += len(x)
    84  		}
    85  	}
    86  	out := make([]byte, total)
    87  	offset := 0
    88  	for _, x := range s {
    89  		l := len(x)
    90  		copy(out[offset:], x)
    91  		offset += l
    92  	}
    93  	return unsafeString(out)
    94  }
    95  
    96  // Serialize constructs a human-readable representation of the parsed state.
    97  //
    98  // Used in tests.
    99  func (e *EvalString) Serialize() string {
   100  	result := ""
   101  	for _, i := range e.Parsed {
   102  		result += "["
   103  		if i.IsSpecial {
   104  			result += "$"
   105  		}
   106  		result += i.Value
   107  		result += "]"
   108  	}
   109  	return result
   110  }
   111  
   112  // Unparse returns the string with variables not expanded.
   113  //
   114  // Used for diagnostics.
   115  func (e *EvalString) Unparse() string {
   116  	result := ""
   117  	for _, i := range e.Parsed {
   118  		special := i.IsSpecial
   119  		if special {
   120  			result += "${"
   121  		}
   122  		result += i.Value
   123  		if special {
   124  			result += "}"
   125  		}
   126  	}
   127  	return result
   128  }
   129  
   130  //
   131  
   132  // IsReservedBinding returns true if the binding name is reserved by ninja.
   133  func IsReservedBinding(v string) bool {
   134  	return v == "command" ||
   135  		v == "depfile" ||
   136  		v == "dyndep" ||
   137  		v == "description" ||
   138  		v == "deps" ||
   139  		v == "generator" ||
   140  		v == "pool" ||
   141  		v == "restat" ||
   142  		v == "rspfile" ||
   143  		v == "rspfile_content" ||
   144  		v == "msvc_deps_prefix"
   145  }
   146  
   147  // Rule is an invocable build command and associated metadata (description,
   148  // etc.).
   149  type Rule struct {
   150  	Name     string
   151  	Bindings map[string]*EvalString
   152  }
   153  
   154  // NewRule returns an initialized Rule.
   155  func NewRule(name string) *Rule {
   156  	return &Rule{
   157  		Name:     name,
   158  		Bindings: map[string]*EvalString{},
   159  	}
   160  }
   161  
   162  func (r *Rule) String() string {
   163  	out := "Rule:" + r.Name + "{"
   164  	names := make([]string, 0, len(r.Bindings))
   165  	for n := range r.Bindings {
   166  		names = append(names, n)
   167  	}
   168  	sort.Strings(names)
   169  	for i, n := range names {
   170  		if i != 0 {
   171  			out += ","
   172  		}
   173  		out += n + ":" + r.Bindings[n].String()
   174  	}
   175  	out += "}"
   176  	return out
   177  }
   178  
   179  //
   180  
   181  // BindingEnv is an Env which contains a mapping of variables to values
   182  // as well as a pointer to a parent scope.
   183  type BindingEnv struct {
   184  	Bindings map[string]string
   185  	Rules    map[string]*Rule
   186  	Parent   *BindingEnv
   187  }
   188  
   189  // NewBindingEnv returns an initialized BindingEnv.
   190  func NewBindingEnv(parent *BindingEnv) *BindingEnv {
   191  	return &BindingEnv{
   192  		Bindings: map[string]string{},
   193  		Rules:    map[string]*Rule{},
   194  		Parent:   parent,
   195  	}
   196  }
   197  
   198  // String serializes the bindings.
   199  func (b *BindingEnv) String() string {
   200  	out := "BindingEnv{"
   201  	if b.Parent != nil {
   202  		out += "(has parent)"
   203  	}
   204  	out += "\n  Bindings:"
   205  	names := make([]string, 0, len(b.Bindings))
   206  	for n := range b.Bindings {
   207  		names = append(names, n)
   208  	}
   209  	sort.Strings(names)
   210  	for _, n := range names {
   211  		out += "\n    " + n + ":" + b.Bindings[n]
   212  	}
   213  	out += "\n  Rules:"
   214  	names = make([]string, 0, len(b.Rules))
   215  	for n := range b.Rules {
   216  		names = append(names, n)
   217  	}
   218  	sort.Strings(names)
   219  	for _, n := range names {
   220  		out += "\n    " + n + ":" + b.Rules[n].String()
   221  	}
   222  	out += "\n}"
   223  	return out
   224  }
   225  
   226  // LookupVariable returns a variable's value.
   227  func (b *BindingEnv) LookupVariable(v string) string {
   228  	if i, ok := b.Bindings[v]; ok {
   229  		return i
   230  	}
   231  	if b.Parent != nil {
   232  		return b.Parent.LookupVariable(v)
   233  	}
   234  	return ""
   235  }
   236  
   237  // LookupRule returns a rule by name.
   238  func (b *BindingEnv) LookupRule(ruleName string) *Rule {
   239  	if i := b.Rules[ruleName]; i != nil {
   240  		return i
   241  	}
   242  	if b.Parent != nil {
   243  		return b.Parent.LookupRule(ruleName)
   244  	}
   245  	return nil
   246  }
   247  
   248  // This is tricky.  Edges want lookup scope to go in this order:
   249  // 1) value set on edge itself (edge->env)
   250  // 2) value set on rule, with expansion in the edge's scope
   251  // 3) value set on enclosing scope of edge (edge->env->parent)
   252  // This function takes as parameters the necessary info to do (2).
   253  func (b *BindingEnv) lookupWithFallback(v string, eval *EvalString, env Env) string {
   254  	if i, ok := b.Bindings[v]; ok {
   255  		return i
   256  	}
   257  	if eval != nil {
   258  		return eval.Evaluate(env)
   259  	}
   260  	if b.Parent != nil {
   261  		return b.Parent.LookupVariable(v)
   262  	}
   263  	return ""
   264  }