From c0dbc553a27823d205194e798ae065fccd6edd89 Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Fri, 2 Jan 2015 15:19:28 -0800 Subject: [PATCH] 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 --- blueprint/context.go | 73 ++++++++++++++++------------- blueprint/parser/parser.go | 94 ++++++++++++++++++++++++++++++++++---- 2 files changed, 126 insertions(+), 41 deletions(-) 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 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, 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 @@ 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") +}