platform_build_blueprint/blueprint/scope.go
Jamie Gennis 0ed63eff29 Fix scoping logic to respect the caller's package.
This change makes the ModuleContext and SingletonContext methods respect the
caller's Go package when performing name lookups.  It does this by re-parenting
the module or singleton's scope to the scope of the caller's package, thus
allowing a Module or Singleton to pass its context to a function defined in
another Go package and have the called function access variables and rules
defined in its own Go package.

Change-Id: Ifdec87ba3095a453b36fb00e38c0bb3a928a2b9b
2014-06-30 18:26:28 -07:00

406 lines
8.8 KiB
Go

package blueprint
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// A Variable represents a global Ninja variable definition that will be written
// to the output .ninja file. A variable may contain references to other global
// Ninja variables, but circular variable references are not allowed.
type Variable interface {
pkg() *pkg
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path.to.pkg.foo"
value(config interface{}) (*ninjaString, error)
String() string
}
// A Pool represents a Ninja pool that will be written to the output .ninja
// file.
type Pool interface {
pkg() *pkg
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path.to.pkg.foo"
def(config interface{}) (*poolDef, error)
String() string
}
// A Rule represents a Ninja build rule that will be written to the output
// .ninja file.
type Rule interface {
pkg() *pkg
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path.to.pkg.foo"
def(config interface{}) (*ruleDef, error)
scope() *basicScope
isArg(argName string) bool
String() string
}
type basicScope struct {
parent *basicScope
variables map[string]Variable
pools map[string]Pool
rules map[string]Rule
imports map[string]*basicScope
}
func newScope(parent *basicScope) *basicScope {
return &basicScope{
parent: parent,
variables: make(map[string]Variable),
pools: make(map[string]Pool),
rules: make(map[string]Rule),
imports: make(map[string]*basicScope),
}
}
func makeRuleScope(parent *basicScope, argNames map[string]bool) *basicScope {
scope := newScope(parent)
for argName := range argNames {
_, err := scope.LookupVariable(argName)
if err != nil {
arg := &argVariable{argName}
err = scope.AddVariable(arg)
if err != nil {
// This should not happen. We should have already checked that
// the name is valid and that the scope doesn't have a variable
// with this name.
panic(err)
}
}
}
// We treat built-in variables like arguments for the purpose of this scope.
for _, builtin := range builtinRuleArgs {
arg := &argVariable{builtin}
err := scope.AddVariable(arg)
if err != nil {
panic(err)
}
}
return scope
}
func (s *basicScope) LookupVariable(name string) (Variable, error) {
dotIndex := strings.IndexRune(name, '.')
if dotIndex >= 0 {
// The variable name looks like "pkg.var"
if dotIndex+1 == len(name) {
return nil, fmt.Errorf("variable name %q ends with a '.'", name)
}
if strings.ContainsRune(name[dotIndex+1:], '.') {
return nil, fmt.Errorf("variable name %q contains multiple '.' "+
"characters", name)
}
pkgName := name[:dotIndex]
varName := name[dotIndex+1:]
first, _ := utf8.DecodeRuneInString(varName)
if !unicode.IsUpper(first) {
return nil, fmt.Errorf("cannot refer to unexported name %q", name)
}
importedScope, err := s.lookupImportedScope(pkgName)
if err != nil {
return nil, err
}
v, ok := importedScope.variables[varName]
if !ok {
return nil, fmt.Errorf("package %q does not contain variable %q",
pkgName, varName)
}
return v, nil
} else {
// The variable name has no package part; just "var"
for ; s != nil; s = s.parent {
v, ok := s.variables[name]
if ok {
return v, nil
}
}
return nil, fmt.Errorf("undefined variable %q", name)
}
}
func (s *basicScope) IsRuleVisible(rule Rule) bool {
_, isBuiltin := rule.(*builtinRule)
if isBuiltin {
return true
}
name := rule.name()
for s != nil {
if s.rules[name] == rule {
return true
}
for _, import_ := range s.imports {
if import_.rules[name] == rule {
return true
}
}
s = s.parent
}
return false
}
func (s *basicScope) IsPoolVisible(pool Pool) bool {
name := pool.name()
for s != nil {
if s.pools[name] == pool {
return true
}
for _, import_ := range s.imports {
if import_.pools[name] == pool {
return true
}
}
s = s.parent
}
return false
}
func (s *basicScope) lookupImportedScope(pkgName string) (*basicScope, error) {
for ; s != nil; s = s.parent {
importedScope, ok := s.imports[pkgName]
if ok {
return importedScope, nil
}
}
return nil, fmt.Errorf("unknown imported package %q (missing call to "+
"blueprint.Import()?)", pkgName)
}
func (s *basicScope) AddImport(name string, importedScope *basicScope) error {
_, present := s.imports[name]
if present {
return fmt.Errorf("import %q is already defined in this scope", name)
}
s.imports[name] = importedScope
return nil
}
func (s *basicScope) AddVariable(v Variable) error {
name := v.name()
_, present := s.variables[name]
if present {
return fmt.Errorf("variable %q is already defined in this scope", name)
}
s.variables[name] = v
return nil
}
func (s *basicScope) AddPool(p Pool) error {
name := p.name()
_, present := s.pools[name]
if present {
return fmt.Errorf("pool %q is already defined in this scope", name)
}
s.pools[name] = p
return nil
}
func (s *basicScope) AddRule(r Rule) error {
name := r.name()
_, present := s.rules[name]
if present {
return fmt.Errorf("rule %q is already defined in this scope", name)
}
s.rules[name] = r
return nil
}
type localScope struct {
namePrefix string
scope *basicScope
}
func newLocalScope(parent *basicScope, namePrefix string) *localScope {
return &localScope{
namePrefix: namePrefix,
scope: newScope(parent),
}
}
// ReparentToCallerPackage sets the localScope's parent scope to the scope of
// the Go package of the caller. This allows a ModuleContext and
// SingletonContext to be passed to a function defined in a different Go package
// with that function having access to all of the package-scoped variables of
// its own package.
//
// The skip argument has the same meaning as the skip argument of
// runtime.Callers.
func (s *localScope) ReparentToCallerPackage(skip int) {
var pkgScope *basicScope
pkgPath, _ := callerName(skip + 1)
pkg, ok := pkgs[pkgPath]
if ok {
pkgScope = pkg.scope
} else {
pkgScope = newScope(nil)
}
s.scope.parent = pkgScope
}
func (s *localScope) LookupVariable(name string) (Variable, error) {
return s.scope.LookupVariable(name)
}
func (s *localScope) IsRuleVisible(rule Rule) bool {
return s.scope.IsRuleVisible(rule)
}
func (s *localScope) IsPoolVisible(pool Pool) bool {
return s.scope.IsPoolVisible(pool)
}
func (s *localScope) AddLocalVariable(name, value string) (*localVariable,
error) {
err := validateNinjaName(name)
if err != nil {
return nil, err
}
if strings.ContainsRune(name, '.') {
return nil, fmt.Errorf("local variable name %q contains '.'", name)
}
ninjaValue, err := parseNinjaString(s.scope, value)
if err != nil {
return nil, err
}
v := &localVariable{
namePrefix: s.namePrefix,
name_: name,
value_: ninjaValue,
}
err = s.scope.AddVariable(v)
if err != nil {
return nil, err
}
return v, nil
}
func (s *localScope) AddLocalRule(name string, params *RuleParams,
argNames ...string) (*localRule, error) {
err := validateNinjaName(name)
if err != nil {
return nil, err
}
err = validateArgNames(argNames)
if err != nil {
return nil, fmt.Errorf("invalid argument name: %s", err)
}
argNamesSet := make(map[string]bool)
for _, argName := range argNames {
argNamesSet[argName] = true
}
ruleScope := makeRuleScope(s.scope, argNamesSet)
def, err := parseRuleParams(ruleScope, params)
if err != nil {
return nil, err
}
r := &localRule{
namePrefix: s.namePrefix,
name_: name,
def_: def,
argNames: argNamesSet,
scope_: ruleScope,
}
err = s.scope.AddRule(r)
if err != nil {
return nil, err
}
return r, nil
}
type localVariable struct {
namePrefix string
name_ string
value_ *ninjaString
}
func (l *localVariable) pkg() *pkg {
return nil
}
func (l *localVariable) name() string {
return l.name_
}
func (l *localVariable) fullName(pkgNames map[*pkg]string) string {
return l.namePrefix + l.name_
}
func (l *localVariable) value(interface{}) (*ninjaString, error) {
return l.value_, nil
}
func (l *localVariable) String() string {
return "<local var>:" + l.namePrefix + l.name_
}
type localRule struct {
namePrefix string
name_ string
def_ *ruleDef
argNames map[string]bool
scope_ *basicScope
}
func (l *localRule) pkg() *pkg {
return nil
}
func (l *localRule) name() string {
return l.name_
}
func (l *localRule) fullName(pkgNames map[*pkg]string) string {
return l.namePrefix + l.name_
}
func (l *localRule) def(interface{}) (*ruleDef, error) {
return l.def_, nil
}
func (r *localRule) scope() *basicScope {
return r.scope_
}
func (r *localRule) isArg(argName string) bool {
return r.argNames[argName]
}
func (r *localRule) String() string {
return "<local rule>:" + r.namePrefix + r.name_
}