Support += assignments
Support += assignments to variables. Variables are now mutable
up until they are referenced, then they become immutable. This
will allow variables to be modified in a conditional, or allow
better commenting on why parts of a variable are set.
Change-Id: Iad964da7206b493365fe3686eedd7954e6eaf9a2
diff --git a/parser/parser.go b/parser/parser.go
index 988080b..7f2f304 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -142,12 +142,15 @@
p.accept(scanner.Ident)
switch p.tok {
+ case '+':
+ p.accept('+')
+ defs = append(defs, p.parseAssignment(ident, pos, "+="))
case '=':
- defs = append(defs, p.parseAssignment(ident, pos))
+ defs = append(defs, p.parseAssignment(ident, pos, "="))
case '{', '(':
defs = append(defs, p.parseModule(ident, pos))
default:
- p.errorf("expected \"=\" or \"{\" or \"(\", found %s",
+ p.errorf("expected \"=\" or \"+=\" or \"{\" or \"(\", found %s",
scanner.TokenString(p.tok))
}
case scanner.EOF:
@@ -161,7 +164,7 @@
}
func (p *parser) parseAssignment(name string,
- namePos scanner.Position) (assignment *Assignment) {
+ namePos scanner.Position, assigner string) (assignment *Assignment) {
assignment = new(Assignment)
@@ -173,10 +176,19 @@
assignment.Name = Ident{name, namePos}
assignment.Value = value
+ assignment.OrigValue = value
assignment.Pos = pos
+ assignment.Assigner = assigner
if p.scope != nil {
- p.scope.Add(assignment)
+ if assigner == "+=" {
+ p.scope.Append(assignment)
+ } else {
+ err := p.scope.Add(assignment)
+ if err != nil {
+ p.errorf("%s", err.Error())
+ }
+ }
}
return
@@ -267,17 +279,10 @@
}
}
-func (p *parser) parseOperator(value1 Value) Value {
- operator := p.tok
- pos := p.scanner.Position
- p.accept(operator)
-
- value2 := p.parseExpression()
-
+func evaluateOperator(value1, value2 Value, operator rune, pos scanner.Position) (Value, error) {
if value1.Type != value2.Type {
- p.errorf("mismatched type in operator %c: %s != %s", operator,
+ return Value{}, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
value1.Type, value2.Type)
- return Value{}
}
value := value1
@@ -292,8 +297,8 @@
value.ListValue = append([]Value{}, value1.ListValue...)
value.ListValue = append(value.ListValue, value2.ListValue...)
default:
- p.errorf("operator %c not supported on type %s", operator, value1.Type)
- return Value{}
+ return Value{}, fmt.Errorf("operator %c not supported on type %s", operator,
+ value1.Type)
}
default:
panic("unknown operator " + string(operator))
@@ -305,6 +310,22 @@
Pos: pos,
}
+ return value, nil
+}
+
+func (p *parser) parseOperator(value1 Value) Value {
+ operator := p.tok
+ pos := p.scanner.Position
+ p.accept(operator)
+
+ value2 := p.parseExpression()
+
+ value, err := evaluateOperator(value1, value2, operator, pos)
+ if err != nil {
+ p.errorf(err.Error())
+ return Value{}
+ }
+
return value
}
@@ -413,6 +434,11 @@
Pos scanner.Position
}
+func (e *Expression) String() string {
+ return fmt.Sprintf("(%s %c %s)@%d:%s", e.Args[0].String(), e.Operator, e.Args[1].String(),
+ e.Pos.Offset, e.Pos)
+}
+
type ValueType int
const (
@@ -443,13 +469,16 @@
}
type Assignment struct {
- Name Ident
- Value Value
- Pos scanner.Position
+ Name Ident
+ Value Value
+ OrigValue Value
+ Pos scanner.Position
+ Assigner string
+ Referenced bool
}
func (a *Assignment) String() string {
- return fmt.Sprintf("%s@%d:%s: %s", a.Name, a.Pos.Offset, a.Pos, a.Value)
+ return fmt.Sprintf("%s@%d:%s %s %s", a.Name, a.Pos.Offset, a.Pos, a.Assigner, a.Value)
}
func (a *Assignment) definitionTag() {}
@@ -506,28 +535,37 @@
}
func (p Value) String() string {
+ var s string
+ if p.Variable != "" {
+ s += p.Variable + " = "
+ }
+ if p.Expression != nil {
+ s += p.Expression.String()
+ }
switch p.Type {
case Bool:
- return fmt.Sprintf("%t@%d:%s", p.BoolValue, p.Pos.Offset, p.Pos)
+ s += fmt.Sprintf("%t@%d:%s", p.BoolValue, p.Pos.Offset, p.Pos)
case String:
- return fmt.Sprintf("%q@%d:%s", p.StringValue, p.Pos.Offset, p.Pos)
+ s += fmt.Sprintf("%q@%d:%s", p.StringValue, p.Pos.Offset, p.Pos)
case List:
valueStrings := make([]string, len(p.ListValue))
for i, value := range p.ListValue {
valueStrings[i] = value.String()
}
- return fmt.Sprintf("@%d:%s-%d:%s[%s]", p.Pos.Offset, p.Pos, p.EndPos.Offset, p.EndPos,
+ s += fmt.Sprintf("@%d:%s-%d:%s[%s]", p.Pos.Offset, p.Pos, p.EndPos.Offset, p.EndPos,
strings.Join(valueStrings, ", "))
case Map:
propertyStrings := make([]string, len(p.MapValue))
for i, property := range p.MapValue {
propertyStrings[i] = property.String()
}
- return fmt.Sprintf("@%d:%s-%d:%s{%s}", p.Pos.Offset, p.Pos, p.EndPos.Offset, p.EndPos,
+ s += fmt.Sprintf("@%d:%s-%d:%s{%s}", p.Pos.Offset, p.Pos, p.EndPos.Offset, p.EndPos,
strings.Join(propertyStrings, ", "))
default:
panic(fmt.Errorf("bad property type: %d", p.Type))
}
+
+ return s
}
type Scope struct {
@@ -558,12 +596,27 @@
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
}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 5007497..f1e99c7 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -292,6 +292,8 @@
foo = "stuff"
bar = foo
baz = foo + bar
+ boo = baz
+ boo += foo
`,
[]Definition{
&Assignment{
@@ -302,6 +304,13 @@
Pos: mkpos(9, 2, 9),
StringValue: "stuff",
},
+ OrigValue: Value{
+ Type: String,
+ Pos: mkpos(9, 2, 9),
+ StringValue: "stuff",
+ },
+ Assigner: "=",
+ Referenced: true,
},
&Assignment{
Name: Ident{"bar", mkpos(19, 3, 3)},
@@ -312,6 +321,14 @@
StringValue: "stuff",
Variable: "foo",
},
+ OrigValue: Value{
+ Type: String,
+ Pos: mkpos(25, 3, 9),
+ StringValue: "stuff",
+ Variable: "foo",
+ },
+ Assigner: "=",
+ Referenced: true,
},
&Assignment{
Name: Ident{"baz", mkpos(31, 4, 3)},
@@ -339,6 +356,118 @@
Pos: mkpos(41, 4, 13),
},
},
+ OrigValue: Value{
+ Type: String,
+ Pos: mkpos(37, 4, 9),
+ StringValue: "stuffstuff",
+ Expression: &Expression{
+ Args: [2]Value{
+ {
+ Type: String,
+ Pos: mkpos(37, 4, 9),
+ StringValue: "stuff",
+ Variable: "foo",
+ },
+ {
+ Type: String,
+ Pos: mkpos(43, 4, 15),
+ StringValue: "stuff",
+ Variable: "bar",
+ },
+ },
+ Operator: '+',
+ Pos: mkpos(41, 4, 13),
+ },
+ },
+ Assigner: "=",
+ Referenced: true,
+ },
+ &Assignment{
+ Name: Ident{"boo", mkpos(49, 5, 3)},
+ Pos: mkpos(53, 5, 7),
+ Value: Value{
+ Type: String,
+ Pos: mkpos(55, 5, 9),
+ StringValue: "stuffstuffstuff",
+ Expression: &Expression{
+ Args: [2]Value{
+ {
+ Type: String,
+ Pos: mkpos(55, 5, 9),
+ StringValue: "stuffstuff",
+ Variable: "baz",
+ Expression: &Expression{
+ Args: [2]Value{
+ {
+ Type: String,
+ Pos: mkpos(37, 4, 9),
+ StringValue: "stuff",
+ Variable: "foo",
+ },
+ {
+ Type: String,
+ Pos: mkpos(43, 4, 15),
+ StringValue: "stuff",
+ Variable: "bar",
+ },
+ },
+ Operator: '+',
+ Pos: mkpos(41, 4, 13),
+ },
+ },
+ {
+ Variable: "foo",
+ Type: String,
+ Pos: mkpos(68, 6, 10),
+ StringValue: "stuff",
+ },
+ },
+ Pos: mkpos(66, 6, 8),
+ Operator: '+',
+ },
+ },
+ OrigValue: Value{
+ Type: String,
+ Pos: mkpos(55, 5, 9),
+ StringValue: "stuffstuff",
+ Variable: "baz",
+ Expression: &Expression{
+ Args: [2]Value{
+ {
+ Type: String,
+ Pos: mkpos(37, 4, 9),
+ StringValue: "stuff",
+ Variable: "foo",
+ },
+ {
+ Type: String,
+ Pos: mkpos(43, 4, 15),
+ StringValue: "stuff",
+ Variable: "bar",
+ },
+ },
+ Operator: '+',
+ Pos: mkpos(41, 4, 13),
+ },
+ },
+ Assigner: "=",
+ },
+ &Assignment{
+ Name: Ident{"boo", mkpos(61, 6, 3)},
+ Pos: mkpos(66, 6, 8),
+ Value: Value{
+ Type: String,
+ Pos: mkpos(68, 6, 10),
+ StringValue: "stuff",
+ Variable: "foo",
+ },
+ OrigValue: Value{
+ Type: String,
+ Pos: mkpos(68, 6, 10),
+ StringValue: "stuff",
+ Variable: "foo",
+ },
+ Assigner: "+=",
},
},
nil,
diff --git a/parser/printer.go b/parser/printer.go
index 1d6cdf6..46ad34f 100644
--- a/parser/printer.go
+++ b/parser/printer.go
@@ -100,8 +100,8 @@
func (p *printer) printAssignment(assignment *Assignment) {
p.printToken(assignment.Name.Name, assignment.Name.Pos, wsDontCare)
- p.printToken("=", assignment.Pos, wsBoth)
- p.printValue(assignment.Value)
+ p.printToken(assignment.Assigner, assignment.Pos, wsBoth)
+ p.printValue(assignment.OrigValue)
p.prev.ws = wsForceBreak
}
diff --git a/parser/printer_test.go b/parser/printer_test.go
index 75dfb6f..5b752e7 100644
--- a/parser/printer_test.go
+++ b/parser/printer_test.go
@@ -119,11 +119,13 @@
foo = "stuff"
bar = foo
baz = foo + bar
+baz += foo
`,
output: `
foo = "stuff"
bar = foo
baz = foo + bar
+baz += foo
`,
},
{