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
diff --git a/blueprint/context.go b/blueprint/context.go
index 788d45b..d43c3af 100644
--- a/blueprint/context.go
+++ b/blueprint/context.go
@@ -145,9 +145,9 @@
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 @@
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
topDownMutator: mutator,
- name: name,
+ name: name,
})
}
@@ -311,7 +311,7 @@
c.mutatorInfo = append(c.mutatorInfo, &mutatorInfo{
bottomUpMutator: mutator,
- name: name,
+ name: name,
})
}
@@ -339,17 +339,19 @@
// 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 @@
// 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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
}
}
- 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,
diff --git a/blueprint/parser/parser.go b/blueprint/parser/parser.go
index 8dd2eba..07b26e8 100644
--- a/blueprint/parser/parser.go
+++ b/blueprint/parser/parser.go
@@ -4,6 +4,7 @@
"errors"
"fmt"
"io"
+ "sort"
"strconv"
"strings"
"text/scanner"
@@ -22,8 +23,8 @@
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 @@
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 @@
assignment.Value = value
assignment.Pos = pos
+ if p.scope != nil {
+ p.scope.Add(assignment)
+ }
+
return
}
@@ -196,7 +203,7 @@
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) 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 @@
}
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 @@
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")
+}