Add basic variable support
Assignments to arbitrary variables are now supported in Blueprint files. Variables cannot be reassigned, and they stay in scope for the remainder of the current Blueprint file and any subdirs Blueprint files. Variables can be used in place of a value in properites or as an entry in a list. Change-Id: I04447e4c541304ded802bb82d1ca4ce07d5d95d2
This commit is contained in:
parent
c902848e5a
commit
c0dbc553a2
2 changed files with 126 additions and 41 deletions
|
@ -145,9 +145,9 @@ type singletonInfo struct {
|
|||
|
||||
type mutatorInfo struct {
|
||||
// set during RegisterMutator
|
||||
topDownMutator TopDownMutator
|
||||
bottomUpMutator BottomUpMutator
|
||||
name string
|
||||
topDownMutator TopDownMutator
|
||||
bottomUpMutator BottomUpMutator
|
||||
name string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
|
@ -293,7 +293,7 @@ func (c *Context) RegisterTopDownMutator(name string, mutator TopDownMutator) {
|
|||
|
||||
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
|
||||
topDownMutator: mutator,
|
||||
name: name,
|
||||
name: name,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -311,7 +311,7 @@ func (c *Context) RegisterBottomUpMutator(name string, mutator BottomUpMutator)
|
|||
|
||||
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
|
||||
bottomUpMutator: mutator,
|
||||
name: name,
|
||||
name: name,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -339,17 +339,19 @@ func (c *Context) SetIgnoreUnknownModuleTypes(ignoreUnknownModuleTypes bool) {
|
|||
// This method should probably not be used directly. It is provided to simplify
|
||||
// testing. Instead ParseBlueprintsFiles should be called to parse a set of
|
||||
// Blueprints files starting from a top-level Blueprints file.
|
||||
func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string,
|
||||
errs []error) {
|
||||
func (c *Context) Parse(rootDir, filename string, r io.Reader,
|
||||
scope *parser.Scope) (subdirs []string, errs []error, outScope *parser.Scope) {
|
||||
|
||||
c.dependenciesReady = false
|
||||
|
||||
relBlueprintsFile, err := filepath.Rel(rootDir, filename)
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
return nil, []error{err}, nil
|
||||
}
|
||||
|
||||
defs, errs := parser.Parse(filename, r)
|
||||
scope = parser.NewScope(scope)
|
||||
scope.Remove("subdirs")
|
||||
defs, errs := parser.Parse(filename, r, scope)
|
||||
if len(errs) > 0 {
|
||||
for i, err := range errs {
|
||||
if parseErr, ok := err.(*parser.ParseError); ok {
|
||||
|
@ -363,7 +365,7 @@ func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string
|
|||
|
||||
// If there were any parse errors don't bother trying to interpret the
|
||||
// result.
|
||||
return nil, errs
|
||||
return nil, errs, nil
|
||||
}
|
||||
|
||||
for _, def := range defs {
|
||||
|
@ -373,12 +375,7 @@ func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string
|
|||
newErrs = c.processModuleDef(def, relBlueprintsFile)
|
||||
|
||||
case *parser.Assignment:
|
||||
var newSubdirs []string
|
||||
newSubdirs, newErrs = c.processAssignment(def)
|
||||
if newSubdirs != nil {
|
||||
subdirs = newSubdirs
|
||||
}
|
||||
|
||||
// Already handled via Scope object
|
||||
default:
|
||||
panic("unknown definition type")
|
||||
}
|
||||
|
@ -391,7 +388,17 @@ func (c *Context) Parse(rootDir, filename string, r io.Reader) (subdirs []string
|
|||
}
|
||||
}
|
||||
|
||||
return subdirs, errs
|
||||
subdirs, newErrs := c.processSubdirs(scope)
|
||||
if len(newErrs) > 0 {
|
||||
errs = append(errs, newErrs...)
|
||||
}
|
||||
|
||||
return subdirs, errs, scope
|
||||
}
|
||||
|
||||
type blueprintAndScope struct {
|
||||
blueprint string
|
||||
scope *parser.Scope
|
||||
}
|
||||
|
||||
// ParseBlueprintsFiles parses a set of Blueprints files starting with the file
|
||||
|
@ -409,7 +416,7 @@ func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string,
|
|||
rootDir := filepath.Dir(rootFile)
|
||||
|
||||
depsSet := map[string]bool{rootFile: true}
|
||||
blueprints := []string{rootFile}
|
||||
blueprints := []blueprintAndScope{blueprintAndScope{rootFile, nil}}
|
||||
|
||||
var file *os.File
|
||||
defer func() {
|
||||
|
@ -425,7 +432,8 @@ func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string,
|
|||
return
|
||||
}
|
||||
|
||||
filename := blueprints[i]
|
||||
filename := blueprints[i].blueprint
|
||||
scope := blueprints[i].scope
|
||||
dir := filepath.Dir(filename)
|
||||
|
||||
file, err = os.Open(filename)
|
||||
|
@ -434,7 +442,7 @@ func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string,
|
|||
continue
|
||||
}
|
||||
|
||||
subdirs, newErrs := c.Parse(rootDir, filename, file)
|
||||
subdirs, newErrs, subScope := c.Parse(rootDir, filename, file, scope)
|
||||
if len(newErrs) > 0 {
|
||||
errs = append(errs, newErrs...)
|
||||
continue
|
||||
|
@ -475,7 +483,11 @@ func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string,
|
|||
// We haven't seen this Blueprints file before, so add
|
||||
// it to our list.
|
||||
depsSet[subBlueprints] = true
|
||||
blueprints = append(blueprints, subBlueprints)
|
||||
blueprints = append(blueprints,
|
||||
blueprintAndScope{
|
||||
subBlueprints,
|
||||
subScope,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -487,7 +499,11 @@ func (c *Context) ParseBlueprintsFiles(rootFile string) (deps []string,
|
|||
subBlueprints := filepath.Join(subdir, "Blueprints")
|
||||
if !depsSet[subBlueprints] {
|
||||
depsSet[subBlueprints] = true
|
||||
blueprints = append(blueprints, subBlueprints)
|
||||
blueprints = append(blueprints,
|
||||
blueprintAndScope{
|
||||
subBlueprints,
|
||||
subScope,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -523,10 +539,10 @@ func listSubdirs(dir string) ([]string, error) {
|
|||
return subdirs, nil
|
||||
}
|
||||
|
||||
func (c *Context) processAssignment(
|
||||
assignment *parser.Assignment) (subdirs []string, errs []error) {
|
||||
func (c *Context) processSubdirs(
|
||||
scope *parser.Scope) (subdirs []string, errs []error) {
|
||||
|
||||
if assignment.Name == "subdirs" {
|
||||
if assignment, err := scope.Get("subdirs"); err == nil {
|
||||
switch assignment.Value.Type {
|
||||
case parser.List:
|
||||
subdirs = make([]string, 0, len(assignment.Value.ListValue))
|
||||
|
@ -574,12 +590,7 @@ func (c *Context) processAssignment(
|
|||
}
|
||||
}
|
||||
|
||||
return nil, []error{
|
||||
&Error{
|
||||
Err: fmt.Errorf("only 'subdirs' assignment is supported"),
|
||||
Pos: assignment.Pos,
|
||||
},
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Context) createVariants(origModule *moduleInfo, mutatorName string,
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
|
@ -22,8 +23,8 @@ func (e *ParseError) Error() string {
|
|||
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
|
||||
}
|
||||
|
||||
func Parse(filename string, r io.Reader) (defs []Definition, errs []error) {
|
||||
p := newParser(r)
|
||||
func Parse(filename string, r io.Reader, scope *Scope) (defs []Definition, errs []error) {
|
||||
p := newParser(r, scope)
|
||||
p.scanner.Filename = filename
|
||||
|
||||
defer func() {
|
||||
|
@ -48,10 +49,12 @@ type parser struct {
|
|||
scanner scanner.Scanner
|
||||
tok rune
|
||||
errors []error
|
||||
scope *Scope
|
||||
}
|
||||
|
||||
func newParser(r io.Reader) *parser {
|
||||
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)
|
||||
|
@ -140,6 +143,10 @@ func (p *parser) parseAssignment(name string,
|
|||
assignment.Value = value
|
||||
assignment.Pos = pos
|
||||
|
||||
if p.scope != nil {
|
||||
p.scope.Add(assignment)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -196,7 +203,7 @@ func (p *parser) parseProperty() (property *Property) {
|
|||
func (p *parser) parseValue() (value Value) {
|
||||
switch p.tok {
|
||||
case scanner.Ident:
|
||||
return p.parseBoolValue()
|
||||
return p.parseVariable()
|
||||
case scanner.String:
|
||||
return p.parseStringValue()
|
||||
case '[':
|
||||
|
@ -210,18 +217,23 @@ func (p *parser) parseValue() (value Value) {
|
|||
}
|
||||
}
|
||||
|
||||
func (p *parser) parseBoolValue() (value Value) {
|
||||
value.Type = Bool
|
||||
func (p *parser) parseVariable() (value Value) {
|
||||
value.Pos = p.scanner.Position
|
||||
switch text := p.scanner.TokenText(); text {
|
||||
case "true":
|
||||
value.Type = Bool
|
||||
value.BoolValue = true
|
||||
case "false":
|
||||
value.Type = Bool
|
||||
value.BoolValue = false
|
||||
default:
|
||||
p.errorf("expected true or false; found %q", text)
|
||||
return
|
||||
assignment, err := p.scope.Get(p.scanner.TokenText())
|
||||
if err != nil {
|
||||
p.errorf(err.Error())
|
||||
}
|
||||
value = assignment.Value
|
||||
}
|
||||
|
||||
p.accept(scanner.Ident)
|
||||
return
|
||||
}
|
||||
|
@ -247,8 +259,13 @@ func (p *parser) parseListValue() (value Value) {
|
|||
}
|
||||
|
||||
var elements []Value
|
||||
for p.tok == scanner.String {
|
||||
elements = append(elements, p.parseStringValue())
|
||||
for p.tok != ']' {
|
||||
element := p.parseValue()
|
||||
if element.Type != String {
|
||||
p.errorf("Expected string in list, found %s", element.String())
|
||||
return
|
||||
}
|
||||
elements = append(elements, element)
|
||||
|
||||
if p.tok != ',' {
|
||||
// There was no comma, so the list is done.
|
||||
|
@ -379,3 +396,60 @@ func (p Value) String() string {
|
|||
panic(fmt.Errorf("bad property type: %d", p.Type))
|
||||
}
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
vars map[string]*Assignment
|
||||
}
|
||||
|
||||
func NewScope(s *Scope) *Scope {
|
||||
newScope := &Scope{
|
||||
vars: make(map[string]*Assignment),
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
for k, v := range s.vars {
|
||||
newScope.vars[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)
|
||||
}
|
||||
|
||||
s.vars[assignment.Name] = assignment
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) Remove(name string) {
|
||||
delete(s.vars, name)
|
||||
}
|
||||
|
||||
func (s *Scope) Get(name string) (*Assignment, error) {
|
||||
if a, ok := s.vars[name]; ok {
|
||||
return a, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("variable %s not set", name)
|
||||
}
|
||||
|
||||
func (s *Scope) String() string {
|
||||
vars := []string{}
|
||||
|
||||
for k := range s.vars {
|
||||
vars = append(vars, k)
|
||||
}
|
||||
|
||||
sort.Strings(vars)
|
||||
|
||||
ret := []string{}
|
||||
for _, v := range vars {
|
||||
ret = append(ret, s.vars[v].String())
|
||||
}
|
||||
|
||||
return strings.Join(ret, "\n")
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue