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:
Colin Cross 2015-01-02 15:19:28 -08:00
parent c902848e5a
commit c0dbc553a2
2 changed files with 126 additions and 41 deletions

View file

@ -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,

View file

@ -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")
}