Merge pull request #44 from colincross/local

Fix bugs related to local vs. inherited variables
diff --git a/context.go b/context.go
index 3892f4e..dd2dc1d 100644
--- a/context.go
+++ b/context.go
@@ -469,12 +469,12 @@
 	}
 	file.Name = relBlueprintsFile
 
-	subdirs, subdirsPos, err := getStringListFromScope(scope, "subdirs")
+	subdirs, subdirsPos, err := getLocalStringListFromScope(scope, "subdirs")
 	if err != nil {
 		errs = append(errs, err)
 	}
 
-	build, buildPos, err := getStringListFromScope(scope, "build")
+	build, buildPos, err := getLocalStringListFromScope(scope, "build")
 	if err != nil {
 		errs = append(errs, err)
 	}
@@ -799,8 +799,10 @@
 	return blueprints, deps, errs
 }
 
-func getStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) {
-	if assignment, err := scope.Get(v); err == nil {
+func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) {
+	if assignment, local := scope.Get(v); assignment == nil || !local {
+		return nil, scanner.Position{}, nil
+	} else {
 		switch assignment.Value.Type {
 		case parser.List:
 			ret := make([]string, 0, len(assignment.Value.ListValue))
@@ -824,12 +826,12 @@
 			panic(fmt.Errorf("unknown value type: %d", assignment.Value.Type))
 		}
 	}
-
-	return nil, scanner.Position{}, nil
 }
 
 func getStringFromScope(scope *parser.Scope, v string) (string, scanner.Position, error) {
-	if assignment, err := scope.Get(v); err == nil {
+	if assignment, _ := scope.Get(v); assignment == nil {
+		return "", scanner.Position{}, nil
+	} else {
 		switch assignment.Value.Type {
 		case parser.String:
 			return assignment.Value.StringValue, assignment.Pos, nil
@@ -842,8 +844,6 @@
 			panic(fmt.Errorf("unknown value type: %d", assignment.Value.Type))
 		}
 	}
-
-	return "", scanner.Position{}, nil
 }
 
 func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
diff --git a/parser/parser.go b/parser/parser.go
index 6027275..fb931af 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -104,13 +104,13 @@
 	return p
 }
 
-func (p *parser) errorf(format string, args ...interface{}) {
+func (p *parser) error(err error) {
 	pos := p.scanner.Position
 	if !pos.IsValid() {
 		pos = p.scanner.Pos()
 	}
-	err := &ParseError{
-		Err: fmt.Errorf(format, args...),
+	err = &ParseError{
+		Err: err,
 		Pos: pos,
 	}
 	p.errors = append(p.errors, err)
@@ -119,6 +119,10 @@
 	}
 }
 
+func (p *parser) errorf(format string, args ...interface{}) {
+	p.error(fmt.Errorf(format, args...))
+}
+
 func (p *parser) accept(toks ...rune) bool {
 	for _, tok := range toks {
 		if p.tok != tok {
@@ -193,18 +197,26 @@
 
 	if p.scope != nil {
 		if assigner == "+=" {
-			if old, err := p.scope.Get(assignment.Name.Name); err == nil {
-				if old.Referenced {
-					p.errorf("modified variable with += after referencing")
+			if old, local := p.scope.Get(assignment.Name.Name); old == nil {
+				p.errorf("modified non-existent variable %q with +=", assignment.Name.Name)
+			} else if !local {
+				p.errorf("modified non-local variable %q with +=", assignment.Name.Name)
+			} else if old.Referenced {
+				p.errorf("modified variable %q with += after referencing",
+					assignment.Name.Name)
+			} else {
+				val, err := p.evaluateOperator(old.Value, assignment.Value, '+', assignment.Pos)
+				if err != nil {
+					p.error(err)
+				} else {
+					old.Value = val
 				}
-				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())
+		} else {
+			err := p.scope.Add(assignment)
+			if err != nil {
+				p.error(err)
+			}
 		}
 	}
 
@@ -392,7 +404,7 @@
 
 	value, err := p.evaluateOperator(value1, value2, operator, pos)
 	if err != nil {
-		p.errorf(err.Error())
+		p.error(err)
 		return Value{}
 	}
 
@@ -427,12 +439,14 @@
 	default:
 		variable := p.scanner.TokenText()
 		if p.eval {
-			assignment, err := p.scope.Get(variable)
-			if err != nil {
-				p.errorf(err.Error())
+			if assignment, local := p.scope.Get(variable); assignment == nil {
+				p.errorf("variable %q is not set", variable)
+			} else {
+				if local {
+					assignment.Referenced = true
+				}
+				value = assignment.Value
 			}
-			assignment.Referenced = true
-			value = assignment.Value
 		}
 		value.Variable = variable
 	}
@@ -684,17 +698,22 @@
 }
 
 type Scope struct {
-	vars map[string]*Assignment
+	vars          map[string]*Assignment
+	inheritedVars map[string]*Assignment
 }
 
 func NewScope(s *Scope) *Scope {
 	newScope := &Scope{
-		vars: make(map[string]*Assignment),
+		vars:          make(map[string]*Assignment),
+		inheritedVars: make(map[string]*Assignment),
 	}
 
 	if s != nil {
 		for k, v := range s.vars {
-			newScope.vars[k] = v
+			newScope.inheritedVars[k] = v
+		}
+		for k, v := range s.inheritedVars {
+			newScope.inheritedVars[k] = v
 		}
 	}
 
@@ -706,6 +725,10 @@
 		return fmt.Errorf("variable already set, previous assignment: %s", old)
 	}
 
+	if old, ok := s.inheritedVars[assignment.Name.Name]; ok {
+		return fmt.Errorf("variable already set in inherited scope, previous assignment: %s", old)
+	}
+
 	s.vars[assignment.Name.Name] = assignment
 
 	return nil
@@ -713,14 +736,19 @@
 
 func (s *Scope) Remove(name string) {
 	delete(s.vars, name)
+	delete(s.inheritedVars, name)
 }
 
-func (s *Scope) Get(name string) (*Assignment, error) {
+func (s *Scope) Get(name string) (*Assignment, bool) {
 	if a, ok := s.vars[name]; ok {
-		return a, nil
+		return a, true
 	}
 
-	return nil, fmt.Errorf("variable %s not set", name)
+	if a, ok := s.inheritedVars[name]; ok {
+		return a, false
+	}
+
+	return nil, false
 }
 
 func (s *Scope) String() string {
@@ -729,12 +757,19 @@
 	for k := range s.vars {
 		vars = append(vars, k)
 	}
+	for k := range s.inheritedVars {
+		vars = append(vars, k)
+	}
 
 	sort.Strings(vars)
 
 	ret := []string{}
 	for _, v := range vars {
-		ret = append(ret, s.vars[v].String())
+		if assignment, ok := s.vars[v]; ok {
+			ret = append(ret, assignment.String())
+		} else {
+			ret = append(ret, s.inheritedVars[v].String())
+		}
 	}
 
 	return strings.Join(ret, "\n")