6437d4e737
Select statements are a new blueprint feature inspired by bazel's select statements. They are essentially alternative syntax for soong config variables that require less boilerplate. In addition, they support making decisions based on a module's variant, which will eliminate the need for manual property struct manipulation, such as the arch mutator's arch: and target: properties. In order to support decisions based on the variant, select statements cannot be evaluated as soon as they're parsed. Instead, they must be stored in the property struct unevaluated. This means that individual properties need to change their type from say, string, to Configurable[string]. Currently, only configurable strings, bools, and string slices are supported, but more types can be added later. The module implementation must call my_property.Evaluate(ctx) in order to get the final, resolved value of the select statement. Bug: 323382414 Test: go tests Change-Id: I62f8721d7f0ac3d1df4a06d7eaa260a5aa7fcba3
818 lines
16 KiB
Go
818 lines
16 KiB
Go
// 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 parser
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/scanner"
|
|
)
|
|
|
|
var errTooManyErrors = errors.New("too many errors")
|
|
|
|
const maxErrors = 1
|
|
|
|
type ParseError struct {
|
|
Err error
|
|
Pos scanner.Position
|
|
}
|
|
|
|
func (e *ParseError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
|
|
}
|
|
|
|
type File struct {
|
|
Name string
|
|
Defs []Definition
|
|
Comments []*CommentGroup
|
|
}
|
|
|
|
func (f *File) Pos() scanner.Position {
|
|
return scanner.Position{
|
|
Filename: f.Name,
|
|
Line: 1,
|
|
Column: 1,
|
|
Offset: 0,
|
|
}
|
|
}
|
|
|
|
func (f *File) End() scanner.Position {
|
|
if len(f.Defs) > 0 {
|
|
return f.Defs[len(f.Defs)-1].End()
|
|
}
|
|
return noPos
|
|
}
|
|
|
|
func parse(p *parser) (file *File, errs []error) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
if r == errTooManyErrors {
|
|
errs = p.errors
|
|
return
|
|
}
|
|
panic(r)
|
|
}
|
|
}()
|
|
|
|
p.next()
|
|
defs := p.parseDefinitions()
|
|
p.accept(scanner.EOF)
|
|
errs = p.errors
|
|
comments := p.comments
|
|
|
|
return &File{
|
|
Name: p.scanner.Filename,
|
|
Defs: defs,
|
|
Comments: comments,
|
|
}, errs
|
|
|
|
}
|
|
|
|
func ParseAndEval(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
|
|
p := newParser(r, scope)
|
|
p.eval = true
|
|
p.scanner.Filename = filename
|
|
|
|
return parse(p)
|
|
}
|
|
|
|
func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
|
|
p := newParser(r, scope)
|
|
p.scanner.Filename = filename
|
|
|
|
return parse(p)
|
|
}
|
|
|
|
func ParseExpression(r io.Reader) (value Expression, errs []error) {
|
|
p := newParser(r, NewScope(nil))
|
|
p.next()
|
|
value = p.parseExpression()
|
|
p.accept(scanner.EOF)
|
|
errs = p.errors
|
|
return
|
|
}
|
|
|
|
type parser struct {
|
|
scanner scanner.Scanner
|
|
tok rune
|
|
errors []error
|
|
scope *Scope
|
|
comments []*CommentGroup
|
|
eval bool
|
|
}
|
|
|
|
func newParser(r io.Reader, scope *Scope) *parser {
|
|
p := &parser{}
|
|
p.scope = scope
|
|
p.scanner.Init(r)
|
|
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
|
|
p.errorf(msg)
|
|
}
|
|
p.scanner.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanStrings |
|
|
scanner.ScanRawStrings | scanner.ScanComments
|
|
return p
|
|
}
|
|
|
|
func (p *parser) error(err error) {
|
|
pos := p.scanner.Position
|
|
if !pos.IsValid() {
|
|
pos = p.scanner.Pos()
|
|
}
|
|
err = &ParseError{
|
|
Err: err,
|
|
Pos: pos,
|
|
}
|
|
p.errors = append(p.errors, err)
|
|
if len(p.errors) >= maxErrors {
|
|
panic(errTooManyErrors)
|
|
}
|
|
}
|
|
|
|
func (p *parser) errorf(format string, args ...interface{}) {
|
|
p.error(fmt.Errorf(format, args...))
|
|
}
|
|
|
|
func (p *parser) accept(toks ...rune) bool {
|
|
for _, tok := range toks {
|
|
if p.tok != tok {
|
|
p.errorf("expected %s, found %s", scanner.TokenString(tok),
|
|
scanner.TokenString(p.tok))
|
|
return false
|
|
}
|
|
p.next()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (p *parser) next() {
|
|
if p.tok != scanner.EOF {
|
|
p.tok = p.scanner.Scan()
|
|
if p.tok == scanner.Comment {
|
|
var comments []*Comment
|
|
for p.tok == scanner.Comment {
|
|
lines := strings.Split(p.scanner.TokenText(), "\n")
|
|
if len(comments) > 0 && p.scanner.Position.Line > comments[len(comments)-1].End().Line+1 {
|
|
p.comments = append(p.comments, &CommentGroup{Comments: comments})
|
|
comments = nil
|
|
}
|
|
comments = append(comments, &Comment{lines, p.scanner.Position})
|
|
p.tok = p.scanner.Scan()
|
|
}
|
|
p.comments = append(p.comments, &CommentGroup{Comments: comments})
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseDefinitions() (defs []Definition) {
|
|
for {
|
|
switch p.tok {
|
|
case scanner.Ident:
|
|
ident := p.scanner.TokenText()
|
|
pos := p.scanner.Position
|
|
|
|
p.accept(scanner.Ident)
|
|
|
|
switch p.tok {
|
|
case '+':
|
|
p.accept('+')
|
|
defs = append(defs, p.parseAssignment(ident, pos, "+="))
|
|
case '=':
|
|
defs = append(defs, p.parseAssignment(ident, pos, "="))
|
|
case '{', '(':
|
|
defs = append(defs, p.parseModule(ident, pos))
|
|
default:
|
|
p.errorf("expected \"=\" or \"+=\" or \"{\" or \"(\", found %s",
|
|
scanner.TokenString(p.tok))
|
|
}
|
|
case scanner.EOF:
|
|
return
|
|
default:
|
|
p.errorf("expected assignment or module definition, found %s",
|
|
scanner.TokenString(p.tok))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseAssignment(name string, namePos scanner.Position,
|
|
assigner string) (assignment *Assignment) {
|
|
|
|
assignment = new(Assignment)
|
|
|
|
pos := p.scanner.Position
|
|
if !p.accept('=') {
|
|
return
|
|
}
|
|
value := p.parseExpression()
|
|
|
|
assignment.Name = name
|
|
assignment.NamePos = namePos
|
|
assignment.Value = value
|
|
assignment.OrigValue = value
|
|
assignment.EqualsPos = pos
|
|
assignment.Assigner = assigner
|
|
|
|
if p.scope != nil {
|
|
if assigner == "+=" {
|
|
if old, local := p.scope.Get(assignment.Name); old == nil {
|
|
p.errorf("modified non-existent variable %q with +=", assignment.Name)
|
|
} else if !local {
|
|
p.errorf("modified non-local variable %q with +=", assignment.Name)
|
|
} else if old.Referenced {
|
|
p.errorf("modified variable %q with += after referencing", assignment.Name)
|
|
} else {
|
|
val, err := p.evaluateOperator(old.Value, assignment.Value, '+', assignment.EqualsPos)
|
|
if err != nil {
|
|
p.error(err)
|
|
} else {
|
|
old.Value = val
|
|
}
|
|
}
|
|
} else {
|
|
err := p.scope.Add(assignment)
|
|
if err != nil {
|
|
p.error(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseModule(typ string, typPos scanner.Position) *Module {
|
|
|
|
compat := false
|
|
lbracePos := p.scanner.Position
|
|
if p.tok == '{' {
|
|
compat = true
|
|
}
|
|
|
|
if !p.accept(p.tok) {
|
|
return nil
|
|
}
|
|
properties := p.parsePropertyList(true, compat)
|
|
rbracePos := p.scanner.Position
|
|
if !compat {
|
|
p.accept(')')
|
|
} else {
|
|
p.accept('}')
|
|
}
|
|
|
|
return &Module{
|
|
Type: typ,
|
|
TypePos: typPos,
|
|
Map: Map{
|
|
Properties: properties,
|
|
LBracePos: lbracePos,
|
|
RBracePos: rbracePos,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
|
|
for p.tok == scanner.Ident {
|
|
property := p.parseProperty(isModule, compat)
|
|
properties = append(properties, property)
|
|
|
|
if p.tok != ',' {
|
|
// There was no comma, so the list is done.
|
|
break
|
|
}
|
|
|
|
p.accept(',')
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseProperty(isModule, compat bool) (property *Property) {
|
|
property = new(Property)
|
|
|
|
name := p.scanner.TokenText()
|
|
namePos := p.scanner.Position
|
|
p.accept(scanner.Ident)
|
|
pos := p.scanner.Position
|
|
|
|
if isModule {
|
|
if compat {
|
|
if !p.accept(':') {
|
|
return
|
|
}
|
|
} else {
|
|
if !p.accept('=') {
|
|
return
|
|
}
|
|
}
|
|
} else {
|
|
if !p.accept(':') {
|
|
return
|
|
}
|
|
}
|
|
|
|
value := p.parseExpression()
|
|
|
|
property.Name = name
|
|
property.NamePos = namePos
|
|
property.Value = value
|
|
property.ColonPos = pos
|
|
|
|
return
|
|
}
|
|
|
|
func (p *parser) parseExpression() (value Expression) {
|
|
value = p.parseValue()
|
|
switch p.tok {
|
|
case '+':
|
|
return p.parseOperator(value)
|
|
case '-':
|
|
p.errorf("subtraction not supported: %s", p.scanner.String())
|
|
return value
|
|
default:
|
|
return value
|
|
}
|
|
}
|
|
|
|
func (p *parser) evaluateOperator(value1, value2 Expression, operator rune,
|
|
pos scanner.Position) (*Operator, error) {
|
|
|
|
value := value1
|
|
|
|
if p.eval {
|
|
e1 := value1.Eval()
|
|
e2 := value2.Eval()
|
|
if e1.Type() != e2.Type() {
|
|
return nil, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
|
|
e1.Type(), e2.Type())
|
|
}
|
|
|
|
if _, ok := e1.(*Select); !ok {
|
|
if _, ok := e2.(*Select); ok {
|
|
// Promote e1 to a select so we can add e2 to it
|
|
e1 = &Select{
|
|
Typ: SelectTypeUnconfigured,
|
|
Cases: []*SelectCase{{
|
|
Pattern: String{
|
|
Value: "__soong_conditions_default__",
|
|
},
|
|
Value: e1,
|
|
}},
|
|
}
|
|
}
|
|
}
|
|
|
|
value = e1.Copy()
|
|
|
|
switch operator {
|
|
case '+':
|
|
switch v := value.(type) {
|
|
case *String:
|
|
v.Value += e2.(*String).Value
|
|
case *Int64:
|
|
v.Value += e2.(*Int64).Value
|
|
v.Token = ""
|
|
case *List:
|
|
v.Values = append(v.Values, e2.(*List).Values...)
|
|
case *Map:
|
|
var err error
|
|
v.Properties, err = p.addMaps(v.Properties, e2.(*Map).Properties, pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
case *Select:
|
|
v.Append = e2
|
|
default:
|
|
return nil, fmt.Errorf("operator %c not supported on type %s", operator, v.Type())
|
|
}
|
|
default:
|
|
panic("unknown operator " + string(operator))
|
|
}
|
|
}
|
|
|
|
return &Operator{
|
|
Args: [2]Expression{value1, value2},
|
|
Operator: operator,
|
|
OperatorPos: pos,
|
|
Value: value,
|
|
}, nil
|
|
}
|
|
|
|
func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) {
|
|
ret := make([]*Property, 0, len(map1))
|
|
|
|
inMap1 := make(map[string]*Property)
|
|
inMap2 := make(map[string]*Property)
|
|
inBoth := make(map[string]*Property)
|
|
|
|
for _, prop1 := range map1 {
|
|
inMap1[prop1.Name] = prop1
|
|
}
|
|
|
|
for _, prop2 := range map2 {
|
|
inMap2[prop2.Name] = prop2
|
|
if _, ok := inMap1[prop2.Name]; ok {
|
|
inBoth[prop2.Name] = prop2
|
|
}
|
|
}
|
|
|
|
for _, prop1 := range map1 {
|
|
if prop2, ok := inBoth[prop1.Name]; ok {
|
|
var err error
|
|
newProp := *prop1
|
|
newProp.Value, err = p.evaluateOperator(prop1.Value, prop2.Value, '+', pos)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, &newProp)
|
|
} else {
|
|
ret = append(ret, prop1)
|
|
}
|
|
}
|
|
|
|
for _, prop2 := range map2 {
|
|
if _, ok := inBoth[prop2.Name]; !ok {
|
|
ret = append(ret, prop2)
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func (p *parser) parseOperator(value1 Expression) *Operator {
|
|
operator := p.tok
|
|
pos := p.scanner.Position
|
|
p.accept(operator)
|
|
|
|
value2 := p.parseExpression()
|
|
|
|
value, err := p.evaluateOperator(value1, value2, operator, pos)
|
|
if err != nil {
|
|
p.error(err)
|
|
return nil
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
func (p *parser) parseValue() (value Expression) {
|
|
switch p.tok {
|
|
case scanner.Ident:
|
|
switch text := p.scanner.TokenText(); text {
|
|
case "true", "false":
|
|
return p.parseBoolean()
|
|
case "select":
|
|
return p.parseSelect()
|
|
default:
|
|
return p.parseVariable()
|
|
}
|
|
case '-', scanner.Int: // Integer might have '-' sign ahead ('+' is only treated as operator now)
|
|
return p.parseIntValue()
|
|
case scanner.String, scanner.RawString:
|
|
return p.parseStringValue()
|
|
case '[':
|
|
return p.parseListValue()
|
|
case '{':
|
|
return p.parseMapValue()
|
|
default:
|
|
p.errorf("expected bool, list, or string value; found %s",
|
|
scanner.TokenString(p.tok))
|
|
return
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseBoolean() Expression {
|
|
switch text := p.scanner.TokenText(); text {
|
|
case "true", "false":
|
|
result := &Bool{
|
|
LiteralPos: p.scanner.Position,
|
|
Value: text == "true",
|
|
Token: text,
|
|
}
|
|
p.accept(scanner.Ident)
|
|
return result
|
|
default:
|
|
p.errorf("Expected true/false, got %q", text)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseVariable() Expression {
|
|
var value Expression
|
|
|
|
text := p.scanner.TokenText()
|
|
if p.eval {
|
|
if assignment, local := p.scope.Get(text); assignment == nil {
|
|
p.errorf("variable %q is not set", text)
|
|
} else {
|
|
if local {
|
|
assignment.Referenced = true
|
|
}
|
|
value = assignment.Value
|
|
}
|
|
} else {
|
|
value = &NotEvaluated{}
|
|
}
|
|
value = &Variable{
|
|
Name: text,
|
|
NamePos: p.scanner.Position,
|
|
Value: value,
|
|
}
|
|
|
|
p.accept(scanner.Ident)
|
|
return value
|
|
}
|
|
|
|
func (p *parser) parseSelect() Expression {
|
|
result := &Select{
|
|
KeywordPos: p.scanner.Position,
|
|
}
|
|
p.accept(scanner.Ident)
|
|
if !p.accept('(') {
|
|
return nil
|
|
}
|
|
switch p.scanner.TokenText() {
|
|
case "release_variable":
|
|
result.Typ = SelectTypeReleaseVariable
|
|
case "soong_config_variable":
|
|
result.Typ = SelectTypeSoongConfigVariable
|
|
case "product_variable":
|
|
result.Typ = SelectTypeProductVariable
|
|
case "variant":
|
|
result.Typ = SelectTypeVariant
|
|
default:
|
|
p.errorf("unknown select type %q, expected release_variable, soong_config_variable, product_variable, or variant", p.scanner.TokenText())
|
|
return nil
|
|
}
|
|
p.accept(scanner.Ident)
|
|
if !p.accept('(') {
|
|
return nil
|
|
}
|
|
|
|
if s := p.parseStringValue(); s != nil {
|
|
result.Condition = *s
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
if result.Typ == SelectTypeSoongConfigVariable {
|
|
if !p.accept(',') {
|
|
return nil
|
|
}
|
|
if s := p.parseStringValue(); s != nil {
|
|
result.Condition.Value += ":" + s.Value
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if !p.accept(')') {
|
|
return nil
|
|
}
|
|
|
|
if !p.accept(',') {
|
|
return nil
|
|
}
|
|
|
|
result.LBracePos = p.scanner.Position
|
|
if !p.accept('{') {
|
|
return nil
|
|
}
|
|
|
|
for p.tok == scanner.String {
|
|
c := &SelectCase{}
|
|
if s := p.parseStringValue(); s != nil {
|
|
if strings.HasPrefix(s.Value, "__soong") {
|
|
p.errorf("select branch conditions starting with __soong are reserved for internal use")
|
|
return nil
|
|
}
|
|
c.Pattern = *s
|
|
} else {
|
|
return nil
|
|
}
|
|
c.ColonPos = p.scanner.Position
|
|
if !p.accept(':') {
|
|
return nil
|
|
}
|
|
c.Value = p.parseExpression()
|
|
if !p.accept(',') {
|
|
return nil
|
|
}
|
|
|
|
result.Cases = append(result.Cases, c)
|
|
}
|
|
|
|
// Default must be last
|
|
if p.tok == scanner.Ident {
|
|
if p.scanner.TokenText() != "_" {
|
|
p.errorf("select cases can either be quoted strings or '_' to match any value")
|
|
return nil
|
|
}
|
|
c := &SelectCase{Pattern: String{
|
|
LiteralPos: p.scanner.Position,
|
|
Value: "__soong_conditions_default__",
|
|
}}
|
|
p.accept(scanner.Ident)
|
|
c.ColonPos = p.scanner.Position
|
|
if !p.accept(':') {
|
|
return nil
|
|
}
|
|
c.Value = p.parseExpression()
|
|
if !p.accept(',') {
|
|
return nil
|
|
}
|
|
result.Cases = append(result.Cases, c)
|
|
}
|
|
|
|
result.RBracePos = p.scanner.Position
|
|
if !p.accept('}') {
|
|
return nil
|
|
}
|
|
if !p.accept(')') {
|
|
return nil
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (p *parser) parseStringValue() *String {
|
|
str, err := strconv.Unquote(p.scanner.TokenText())
|
|
if err != nil {
|
|
p.errorf("couldn't parse string: %s", err)
|
|
return nil
|
|
}
|
|
|
|
value := &String{
|
|
LiteralPos: p.scanner.Position,
|
|
Value: str,
|
|
}
|
|
p.accept(p.tok)
|
|
return value
|
|
}
|
|
|
|
func (p *parser) parseIntValue() *Int64 {
|
|
var str string
|
|
literalPos := p.scanner.Position
|
|
if p.tok == '-' {
|
|
str += string(p.tok)
|
|
p.accept(p.tok)
|
|
if p.tok != scanner.Int {
|
|
p.errorf("expected int; found %s", scanner.TokenString(p.tok))
|
|
return nil
|
|
}
|
|
}
|
|
str += p.scanner.TokenText()
|
|
i, err := strconv.ParseInt(str, 10, 64)
|
|
if err != nil {
|
|
p.errorf("couldn't parse int: %s", err)
|
|
return nil
|
|
}
|
|
|
|
value := &Int64{
|
|
LiteralPos: literalPos,
|
|
Value: i,
|
|
Token: str,
|
|
}
|
|
p.accept(scanner.Int)
|
|
return value
|
|
}
|
|
|
|
func (p *parser) parseListValue() *List {
|
|
lBracePos := p.scanner.Position
|
|
if !p.accept('[') {
|
|
return nil
|
|
}
|
|
|
|
var elements []Expression
|
|
for p.tok != ']' {
|
|
element := p.parseExpression()
|
|
elements = append(elements, element)
|
|
|
|
if p.tok != ',' {
|
|
// There was no comma, so the list is done.
|
|
break
|
|
}
|
|
|
|
p.accept(',')
|
|
}
|
|
|
|
rBracePos := p.scanner.Position
|
|
p.accept(']')
|
|
|
|
return &List{
|
|
LBracePos: lBracePos,
|
|
RBracePos: rBracePos,
|
|
Values: elements,
|
|
}
|
|
}
|
|
|
|
func (p *parser) parseMapValue() *Map {
|
|
lBracePos := p.scanner.Position
|
|
if !p.accept('{') {
|
|
return nil
|
|
}
|
|
|
|
properties := p.parsePropertyList(false, false)
|
|
|
|
rBracePos := p.scanner.Position
|
|
p.accept('}')
|
|
|
|
return &Map{
|
|
LBracePos: lBracePos,
|
|
RBracePos: rBracePos,
|
|
Properties: properties,
|
|
}
|
|
}
|
|
|
|
type Scope struct {
|
|
vars map[string]*Assignment
|
|
inheritedVars map[string]*Assignment
|
|
}
|
|
|
|
func NewScope(s *Scope) *Scope {
|
|
newScope := &Scope{
|
|
vars: make(map[string]*Assignment),
|
|
inheritedVars: make(map[string]*Assignment),
|
|
}
|
|
|
|
if s != nil {
|
|
for k, v := range s.vars {
|
|
newScope.inheritedVars[k] = v
|
|
}
|
|
for k, v := range s.inheritedVars {
|
|
newScope.inheritedVars[k] = v
|
|
}
|
|
}
|
|
|
|
return newScope
|
|
}
|
|
|
|
func (s *Scope) Add(assignment *Assignment) error {
|
|
if old, ok := s.vars[assignment.Name]; ok {
|
|
return fmt.Errorf("variable already set, previous assignment: %s", old)
|
|
}
|
|
|
|
if old, ok := s.inheritedVars[assignment.Name]; ok {
|
|
return fmt.Errorf("variable already set in inherited scope, previous assignment: %s", old)
|
|
}
|
|
|
|
s.vars[assignment.Name] = assignment
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *Scope) Remove(name string) {
|
|
delete(s.vars, name)
|
|
delete(s.inheritedVars, name)
|
|
}
|
|
|
|
func (s *Scope) Get(name string) (*Assignment, bool) {
|
|
if a, ok := s.vars[name]; ok {
|
|
return a, true
|
|
}
|
|
|
|
if a, ok := s.inheritedVars[name]; ok {
|
|
return a, false
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func (s *Scope) String() string {
|
|
vars := []string{}
|
|
|
|
for k := range s.vars {
|
|
vars = append(vars, k)
|
|
}
|
|
for k := range s.inheritedVars {
|
|
vars = append(vars, k)
|
|
}
|
|
|
|
sort.Strings(vars)
|
|
|
|
ret := []string{}
|
|
for _, v := range vars {
|
|
if assignment, ok := s.vars[v]; ok {
|
|
ret = append(ret, assignment.String())
|
|
} else {
|
|
ret = append(ret, s.inheritedVars[v].String())
|
|
}
|
|
}
|
|
|
|
return strings.Join(ret, "\n")
|
|
}
|