Allow parsing Blueprints files without evaluating

Running bpfmt or bpmodify on a Blueprints file that references
variables defined in a parent Blueprints file causes parse errors
on unknown variables.  Modify the parser to parse in two modes,
parser.Parse and parser.ParseAndEval.  The first parses without
attempting to evaluate variables and expressions, the second
performs a full evaluation.

Change-Id: Ic11948ea77c8e65379d7260591ada15db0e4f5b9
This commit is contained in:
Colin Cross 2015-03-19 17:28:06 -07:00
parent 85398a916a
commit 96e5670496
3 changed files with 77 additions and 69 deletions

View file

@ -430,7 +430,7 @@ func (c *Context) parse(rootDir, filename string, r io.Reader,
scope = parser.NewScope(scope)
scope.Remove("subdirs")
file, errs := parser.Parse(filename, r, scope)
file, errs := parser.ParseAndEval(filename, r, scope)
if len(errs) > 0 {
for i, err := range errs {
if parseErr, ok := err.(*parser.ParseError); ok {

View file

@ -42,11 +42,7 @@ type File struct {
Comments []Comment
}
func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error) {
p := newParser(r, scope)
p.scanner.Filename = filename
func parse(p *parser) (file *File, errs []error) {
defer func() {
if r := recover(); r != nil {
if r == errTooManyErrors {
@ -66,30 +62,42 @@ func Parse(filename string, r io.Reader, scope *Scope) (file *File, errs []error
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)
}
type parser struct {
scanner scanner.Scanner
tok rune
errors []error
scope *Scope
parseComments bool
comments []Comment
scanner scanner.Scanner
tok rune
errors []error
scope *Scope
comments []Comment
eval bool
}
func newParser(r io.Reader, scope *Scope) *parser {
p := &parser{}
p.scope = scope
p.parseComments = true
p.scanner.Init(r)
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
p.errorf(msg)
}
p.scanner.Mode = scanner.ScanIdents | scanner.ScanStrings |
scanner.ScanRawStrings | scanner.ScanComments
if !p.parseComments {
p.scanner.Mode |= scanner.SkipComments
}
p.next()
return p
}
@ -183,13 +191,19 @@ func (p *parser) parseAssignment(name string,
if p.scope != nil {
if assigner == "+=" {
p.scope.Append(assignment)
} else {
err := p.scope.Add(assignment)
if err != nil {
p.errorf("%s", err.Error())
if old, err := p.scope.Get(assignment.Name.Name); err == nil {
if old.Referenced {
p.errorf("modified variable with += after referencing")
}
old.Value, err = p.evaluateOperator(old.Value, assignment.Value, '+', assignment.Pos)
return
}
}
err := p.scope.Add(assignment)
if err != nil {
p.errorf("%s", err.Error())
}
}
return
@ -280,35 +294,41 @@ func (p *parser) parseExpression() (value Value) {
}
}
func evaluateOperator(value1, value2 Value, operator rune, pos scanner.Position) (Value, error) {
if value1.Type != value2.Type {
return Value{}, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
value1.Type, value2.Type)
}
func (p *parser) evaluateOperator(value1, value2 Value, operator rune,
pos scanner.Position) (Value, error) {
value := value1
value.Variable = ""
value := Value{}
switch operator {
case '+':
switch value1.Type {
case String:
value.StringValue = value1.StringValue + value2.StringValue
case List:
value.ListValue = append([]Value{}, value1.ListValue...)
value.ListValue = append(value.ListValue, value2.ListValue...)
case Map:
var err error
value.MapValue, err = addMaps(value.MapValue, value2.MapValue, pos)
if err != nil {
return Value{}, err
if p.eval {
if value1.Type != value2.Type {
return Value{}, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
value1.Type, value2.Type)
}
value = value1
value.Variable = ""
switch operator {
case '+':
switch value1.Type {
case String:
value.StringValue = value1.StringValue + value2.StringValue
case List:
value.ListValue = append([]Value{}, value1.ListValue...)
value.ListValue = append(value.ListValue, value2.ListValue...)
case Map:
var err error
value.MapValue, err = p.addMaps(value.MapValue, value2.MapValue, pos)
if err != nil {
return Value{}, err
}
default:
return Value{}, fmt.Errorf("operator %c not supported on type %s", operator,
value1.Type)
}
default:
return Value{}, fmt.Errorf("operator %c not supported on type %s", operator,
value1.Type)
panic("unknown operator " + string(operator))
}
default:
panic("unknown operator " + string(operator))
}
value.Expression = &Expression{
@ -320,7 +340,7 @@ func evaluateOperator(value1, value2 Value, operator rune, pos scanner.Position)
return value, nil
}
func addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) {
func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) {
ret := make([]*Property, 0, len(map1))
inMap1 := make(map[string]*Property)
@ -342,7 +362,7 @@ func addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error)
if prop2, ok := inBoth[prop1.Name.Name]; ok {
var err error
newProp := *prop1
newProp.Value, err = evaluateOperator(prop1.Value, prop2.Value, '+', pos)
newProp.Value, err = p.evaluateOperator(prop1.Value, prop2.Value, '+', pos)
if err != nil {
return nil, err
}
@ -368,7 +388,7 @@ func (p *parser) parseOperator(value1 Value) Value {
value2 := p.parseExpression()
value, err := evaluateOperator(value1, value2, operator, pos)
value, err := p.evaluateOperator(value1, value2, operator, pos)
if err != nil {
p.errorf(err.Error())
return Value{}
@ -404,11 +424,14 @@ func (p *parser) parseVariable() (value Value) {
value.BoolValue = false
default:
variable := p.scanner.TokenText()
assignment, err := p.scope.Get(variable)
if err != nil {
p.errorf(err.Error())
if p.eval {
assignment, err := p.scope.Get(variable)
if err != nil {
p.errorf(err.Error())
}
assignment.Referenced = true
value = assignment.Value
}
value = assignment.Value
value.Variable = variable
}
value.Pos = p.scanner.Position
@ -440,7 +463,7 @@ func (p *parser) parseListValue() (value Value) {
var elements []Value
for p.tok != ']' {
element := p.parseExpression()
if element.Type != String {
if p.eval && element.Type != String {
p.errorf("Expected string in list, found %s", element.String())
return
}
@ -644,27 +667,12 @@ func (s *Scope) Add(assignment *Assignment) error {
return nil
}
func (s *Scope) Append(assignment *Assignment) error {
var err error
if old, ok := s.vars[assignment.Name.Name]; ok {
if old.Referenced {
return fmt.Errorf("modified variable with += after referencing")
}
old.Value, err = evaluateOperator(old.Value, assignment.Value, '+', assignment.Pos)
} else {
err = s.Add(assignment)
}
return err
}
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 {
a.Referenced = true
return a, nil
}

View file

@ -477,7 +477,7 @@ var validParseTestCases = []struct {
func TestParseValidInput(t *testing.T) {
for _, testCase := range validParseTestCases {
r := bytes.NewBufferString(testCase.input)
file, errs := Parse("", r, NewScope(nil))
file, errs := ParseAndEval("", r, NewScope(nil))
if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected errors:")