| /* |
| 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] |
| } |