blob: 40394af6b11613cba297aaecc635d8cd1fbe6f14 [file] [log] [blame] [edit]
/*
Copyright 2020 Google LLC
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
https://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 bzlenv provides function to create and update a static environment.
package bzlenv
import (
"github.com/bazelbuild/buildtools/build"
)
// ValueKind describes how a binding was declared.
type ValueKind int
// List of ValueKind values.
const (
Builtin ValueKind = iota // language builtin
Imported // declared with load()
Global // declared with assignment on top-level
Function // declared with a def
Parameter // function parameter
Local // local variable, defined with assignment or as a loop variable
)
func (k ValueKind) String() string {
switch k {
case Builtin:
return "builtin"
case Imported:
return "imported"
case Global:
return "global"
case Function:
return "function"
case Parameter:
return "parameter"
case Local:
return "local"
default:
panic(k)
}
}
// NameInfo represents information about a symbol name.
type NameInfo struct {
ID int // unique identifier
Name string // name of the variable (not unique)
Kind ValueKind
Definition build.Expr // node that defines the value
}
type block map[string]NameInfo
// Environment represents a static environment (e.g. information about all available symbols).
type Environment struct {
Blocks []block
Function *build.DefStmt // enclosing function (or nil on top-level)
nextID int // used to create unique identifiers
Stack []build.Expr // parents of the current node
}
// NewEnvironment creates a new empty Environment.
func NewEnvironment() *Environment {
sc := block{}
return &Environment{[]block{sc}, nil, 0, []build.Expr{}}
}
func (e *Environment) enterBlock() {
e.Blocks = append(e.Blocks, block{})
}
func (e *Environment) exitBlock() {
if len(e.Blocks) < 1 {
panic("no block to close")
}
e.Blocks = e.Blocks[:len(e.Blocks)-1]
}
func (e *Environment) currentBlock() block {
return e.Blocks[len(e.Blocks)-1]
}
func (sc *block) declare(name string, kind ValueKind, definition build.Expr, id int) {
(*sc)[name] = NameInfo{
ID: id,
Name: name,
Definition: definition,
Kind: kind}
}
// Get resolves the name and resolves information about the binding (or nil if it's not defined).
func (e *Environment) Get(name string) *NameInfo {
for i := len(e.Blocks) - 1; i >= 0; i-- {
if ret, ok := e.Blocks[i][name]; ok {
return &ret
}
}
return nil
}
func (e *Environment) declare(name string, kind ValueKind, node build.Expr) {
sc := e.currentBlock()
sc.declare(name, kind, node, e.nextID)
e.nextID++
}
func declareGlobals(stmts []build.Expr, env *Environment) {
for _, node := range stmts {
switch node := node.(type) {
case *build.LoadStmt:
for _, ident := range node.To {
env.declare(ident.Name, Imported, ident)
}
case *build.AssignExpr:
kind := Local
if env.Function == nil {
kind = Global
}
for _, id := range CollectLValues(node.LHS) {
env.declare(id.Name, kind, node)
}
case *build.DefStmt:
env.declare(node.Name, Function, node)
}
}
}
// CollectLValues returns the list of identifiers that are assigned (assuming that node is a valid
// LValue). For example, it returns `a`, `b` and `c` for the input `a, (b, c)`.
func CollectLValues(node build.Expr) []*build.Ident {
var result []*build.Ident
switch node := node.(type) {
case *build.Ident:
result = append(result, node)
case *build.TupleExpr:
for _, item := range node.List {
result = append(result, CollectLValues(item)...)
}
case *build.ListExpr:
for _, item := range node.List {
result = append(result, CollectLValues(item)...)
}
}
return result
}
func declareParams(fct *build.DefStmt, env *Environment) {
for _, node := range fct.Params {
name, _ := build.GetParamName(node)
env.declare(name, Parameter, node)
}
}
func declareLocalVariables(stmts []build.Expr, env *Environment) {
for _, stmt := range stmts {
switch node := stmt.(type) {
case *build.AssignExpr:
kind := Local
if env.Function == nil {
kind = Global
}
for _, id := range CollectLValues(node.LHS) {
env.declare(id.Name, kind, node)
}
case *build.IfStmt:
declareLocalVariables(node.True, env)
declareLocalVariables(node.False, env)
case *build.ForStmt:
for _, id := range CollectLValues(node.Vars) {
env.declare(id.Name, Local, node)
}
declareLocalVariables(node.Body, env)
}
}
}
// WalkOnceWithEnvironment calls fct on every child of node, while maintaining the Environment of all available symbols.
func WalkOnceWithEnvironment(node build.Expr, env *Environment, fct func(e *build.Expr, env *Environment)) {
env.Stack = append(env.Stack, node)
switch node := node.(type) {
case *build.File:
declareGlobals(node.Stmt, env)
build.WalkOnce(node, func(e *build.Expr) { fct(e, env) })
case *build.DefStmt:
env.enterBlock()
env.Function = node
declareParams(node, env)
declareLocalVariables(node.Body, env)
build.WalkOnce(node, func(e *build.Expr) { fct(e, env) })
env.Function = nil
env.exitBlock()
case *build.Comprehension:
env.enterBlock()
for _, clause := range node.Clauses {
switch clause := clause.(type) {
case *build.ForClause:
for _, id := range CollectLValues(clause.Vars) {
env.declare(id.Name, Local, node)
}
}
}
build.WalkOnce(node, func(e *build.Expr) { fct(e, env) })
env.exitBlock()
default:
build.WalkOnce(node, func(e *build.Expr) { fct(e, env) })
}
env.Stack = env.Stack[:len(env.Stack)-1]
}