diff options
-rw-r--r-- | Blueprints | 1 | ||||
-rw-r--r-- | bpmodify/bpmodify.go | 34 | ||||
-rw-r--r-- | build.ninja.in | 21 | ||||
-rw-r--r-- | context.go | 23 | ||||
-rw-r--r-- | parser/ast.go | 374 | ||||
-rw-r--r-- | parser/modify.go | 39 | ||||
-rw-r--r-- | parser/parser.go | 402 | ||||
-rw-r--r-- | parser/parser_test.go | 529 | ||||
-rw-r--r-- | parser/printer.go | 94 | ||||
-rw-r--r-- | parser/printer_test.go | 43 | ||||
-rw-r--r-- | parser/sort.go | 95 | ||||
-rw-r--r-- | unpack.go | 51 | ||||
-rw-r--r-- | unpack_test.go | 405 |
13 files changed, 1220 insertions, 891 deletions
@@ -32,6 +32,7 @@ bootstrap_go_package( name = "blueprint-parser", pkgPath = "github.com/google/blueprint/parser", srcs = [ + "parser/ast.go", "parser/modify.go", "parser/parser.go", "parser/printer.go", diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go index f4216dd..6f7a5bf 100644 --- a/bpmodify/bpmodify.go +++ b/bpmodify/bpmodify.go @@ -9,7 +9,6 @@ import ( "bytes" "flag" "fmt" - "github.com/google/blueprint/parser" "io" "io/ioutil" "os" @@ -17,6 +16,8 @@ import ( "path/filepath" "strings" "unicode" + + "github.com/google/blueprint/parser" ) var ( @@ -123,8 +124,8 @@ func findModules(file *parser.File) (modified bool, errs []error) { for _, def := range file.Defs { if module, ok := def.(*parser.Module); ok { for _, prop := range module.Properties { - if prop.Name.Name == "name" && prop.Value.Type == parser.String { - if targetedModule(prop.Value.StringValue) { + if prop.Name.Name == "name" && prop.Value.Type() == parser.StringType { + if targetedModule(prop.Value.Eval().(*parser.String).Value) { m, newErrs := processModule(module, prop.Name.Name, file) errs = append(errs, newErrs...) modified = modified || m @@ -142,7 +143,7 @@ func processModule(module *parser.Module, moduleName string, for _, prop := range module.Properties { if prop.Name.Name == *parameter { - modified, errs = processParameter(&prop.Value, *parameter, moduleName, file) + modified, errs = processParameter(prop.Value, *parameter, moduleName, file) return } } @@ -150,37 +151,38 @@ func processModule(module *parser.Module, moduleName string, return false, nil } -func processParameter(value *parser.Value, paramName, moduleName string, +func processParameter(value parser.Expression, paramName, moduleName string, file *parser.File) (modified bool, errs []error) { - if value.Type != parser.List { - return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s", - paramName, moduleName, value.Type.String())} - } - - if value.Variable != "" { + if _, ok := value.(*parser.Variable); ok { return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported", paramName, moduleName)} } - if value.Expression != nil { + if _, ok := value.(*parser.Operator); ok { return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported", paramName, moduleName)} } - wasSorted := parser.ListIsSorted(*value) + list, ok := value.(*parser.List) + if !ok { + return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s", + paramName, moduleName, value.Type().String())} + } + + wasSorted := parser.ListIsSorted(list) for _, a := range addIdents.idents { - m := parser.AddStringToList(value, a) + m := parser.AddStringToList(list, a) modified = modified || m } for _, r := range removeIdents.idents { - m := parser.RemoveStringFromList(value, r) + m := parser.RemoveStringFromList(list, r) modified = modified || m } if (wasSorted || *sortLists) && modified { - parser.SortList(file, *value) + parser.SortList(file, list) } return modified, nil diff --git a/build.ninja.in b/build.ninja.in index 9ba88c8..b70ea22 100644 --- a/build.ninja.in +++ b/build.ninja.in @@ -81,7 +81,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 -# Defined: Blueprints:80:1 +# Defined: Blueprints:81:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $ @@ -108,7 +108,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 -# Defined: Blueprints:99:1 +# Defined: Blueprints:100:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $ @@ -128,7 +128,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 -# Defined: Blueprints:46:1 +# Defined: Blueprints:47:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $ @@ -147,7 +147,8 @@ default $ build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $ - : g.bootstrap.compile ${g.bootstrap.srcDir}/parser/modify.go $ + : g.bootstrap.compile ${g.bootstrap.srcDir}/parser/ast.go $ + ${g.bootstrap.srcDir}/parser/modify.go $ ${g.bootstrap.srcDir}/parser/parser.go $ ${g.bootstrap.srcDir}/parser/printer.go $ ${g.bootstrap.srcDir}/parser/sort.go | ${g.bootstrap.compileCmd} @@ -160,7 +161,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 -# Defined: Blueprints:52:1 +# Defined: Blueprints:53:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $ @@ -175,7 +176,7 @@ default $ # Variant: # Type: bootstrap_go_package # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 -# Defined: Blueprints:64:1 +# Defined: Blueprints:65:1 build $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $ @@ -193,7 +194,7 @@ default $ # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 -# Defined: Blueprints:142:1 +# Defined: Blueprints:143:1 build ${g.bootstrap.buildDir}/.bootstrap/choosestage/obj/choosestage.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/choosestage/choosestage.go | $ @@ -216,7 +217,7 @@ default ${g.bootstrap.BinDir}/choosestage # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 -# Defined: Blueprints:132:1 +# Defined: Blueprints:133:1 build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $ @@ -239,7 +240,7 @@ default ${g.bootstrap.BinDir}/gotestmain # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 -# Defined: Blueprints:137:1 +# Defined: Blueprints:138:1 build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $ @@ -262,7 +263,7 @@ default ${g.bootstrap.BinDir}/gotestrunner # Variant: # Type: bootstrap_core_go_binary # Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 -# Defined: Blueprints:111:1 +# Defined: Blueprints:112:1 build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $ g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | $ @@ -861,21 +861,22 @@ func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scann 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)) + switch value := assignment.Value.Eval().(type) { + case *parser.List: + ret := make([]string, 0, len(value.Values)) - for _, value := range assignment.Value.ListValue { - if value.Type != parser.String { + for _, listValue := range value.Values { + s, ok := listValue.(*parser.String) + if !ok { // The parser should not produce this. panic("non-string value found in list") } - ret = append(ret, value.StringValue) + ret = append(ret, s.Value) } return ret, assignment.Pos, nil - case parser.Bool, parser.String: + case *parser.Bool, *parser.String: return nil, scanner.Position{}, &Error{ Err: fmt.Errorf("%q must be a list of strings", v), Pos: assignment.Pos, @@ -890,10 +891,10 @@ func getStringFromScope(scope *parser.Scope, v string) (string, scanner.Position 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 - case parser.Bool, parser.List: + switch value := assignment.Value.Eval().(type) { + case *parser.String: + return value.Value, assignment.Pos, nil + case *parser.Bool, *parser.List: return "", scanner.Position{}, &Error{ Err: fmt.Errorf("%q must be a string", v), Pos: assignment.Pos, diff --git a/parser/ast.go b/parser/ast.go new file mode 100644 index 0000000..387f6d5 --- /dev/null +++ b/parser/ast.go @@ -0,0 +1,374 @@ +// Copyright 2016 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package parser + +import ( + "fmt" + "strings" + "text/scanner" +) + +// Definition is an Assignment or a Module at the top level of a Blueprints file +type Definition interface { + String() string + definitionTag() +} + +// An Assignment is a variable assignment at the top level of a Blueprints file, scoped to the +// file and and subdirs. +type Assignment struct { + Name Ident + Value Expression + OrigValue Expression + Pos scanner.Position + Assigner string + Referenced bool +} + +func (a *Assignment) String() string { + return fmt.Sprintf("%s@%s %s %s (%s) %t", a.Name, a.Pos, a.Assigner, a.Value, a.OrigValue, a.Referenced) +} + +func (a *Assignment) definitionTag() {} + +// A Module is a module definition at the top level of a Blueprints file +type Module struct { + Type Ident + Map +} + +func (m *Module) Copy() *Module { + ret := *m + ret.Properties = make([]*Property, len(m.Properties)) + for i := range m.Properties { + ret.Properties[i] = m.Properties[i].Copy() + } + return &ret +} + +func (m *Module) String() string { + propertyStrings := make([]string, len(m.Properties)) + for i, property := range m.Properties { + propertyStrings[i] = property.String() + } + return fmt.Sprintf("%s@%s-%s{%s}", m.Type, + m.LBracePos, m.RBracePos, + strings.Join(propertyStrings, ", ")) +} + +func (m *Module) definitionTag() {} + +// A Property is a name: value pair within a Map, which may be a top level Module. +type Property struct { + Name Ident + Value Expression + Pos scanner.Position +} + +func (p *Property) Copy() *Property { + ret := *p + ret.Value = p.Value.Copy() + return &ret +} + +func (p *Property) String() string { + return fmt.Sprintf("%s@%s: %s", p.Name, p.Pos, p.Value) +} + +// An Ident is a name identifier, the Type of a Module, the Name of a Property, or the Name of a +// Variable. +type Ident struct { + Name string + Pos scanner.Position +} + +func (i Ident) String() string { + return fmt.Sprintf("%s@%s", i.Name, i.Pos) +} + +// An Expression is a Value in a Property or Assignment. It can be a literal (String or Bool), a +// Map, a List, an Operator that combines two expressions of the same type, or a Variable that +// references and Assignment. +type Expression interface { + // Copy returns a copy of the Expression that will not affect the original if mutated + Copy() Expression + String() string + // Type returns the underlying Type enum of the Expression if it were to be evalutated + Type() Type + // Pos returns the position of the first token in the Expression + Pos() scanner.Position + // End returns the position of the beginning of the last token in the Expression + End() scanner.Position + // Eval returns an expression that is fully evaluated to a simple type (List, Map, String, or + // Bool). It will return the same object for every call to Eval(). + Eval() Expression +} + +type Type int + +const ( + BoolType Type = iota + 1 + StringType + ListType + MapType +) + +func (t Type) String() string { + switch t { + case BoolType: + return "bool" + case StringType: + return "string" + case ListType: + return "list" + case MapType: + return "map" + default: + panic(fmt.Errorf("Unknown type %d", t)) + } +} + +type Operator struct { + Args [2]Expression + Operator rune + OperatorPos scanner.Position + Value Expression +} + +func (x *Operator) Copy() Expression { + ret := *x + ret.Args[0] = x.Args[0].Copy() + ret.Args[1] = x.Args[1].Copy() + return &ret +} + +func (x *Operator) Eval() Expression { + return x.Value.Eval() +} + +func (x *Operator) Type() Type { + return x.Args[0].Type() +} + +func (x *Operator) Pos() scanner.Position { return x.Args[0].Pos() } +func (x *Operator) End() scanner.Position { return x.Args[1].End() } + +func (x *Operator) String() string { + return fmt.Sprintf("(%s %c %s = %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(), + x.Value, x.OperatorPos) +} + +type Variable struct { + Name string + Value Expression + NamePos scanner.Position +} + +func (x *Variable) Pos() scanner.Position { return x.NamePos } +func (x *Variable) End() scanner.Position { return x.NamePos } + +func (x *Variable) Copy() Expression { + ret := *x + return &ret +} + +func (x *Variable) Eval() Expression { + return x.Value.Eval() +} + +func (x *Variable) String() string { + return x.Name + " = " + x.Value.String() +} + +func (x *Variable) Type() Type { return x.Value.Type() } + +type Map struct { + LBracePos scanner.Position + RBracePos scanner.Position + Properties []*Property +} + +func (x *Map) Pos() scanner.Position { return x.LBracePos } +func (x *Map) End() scanner.Position { return x.RBracePos } + +func (x *Map) Copy() Expression { + ret := *x + ret.Properties = make([]*Property, len(x.Properties)) + for i := range x.Properties { + ret.Properties[i] = x.Properties[i].Copy() + } + return &ret +} + +func (x *Map) Eval() Expression { + return x +} + +func (x *Map) String() string { + propertyStrings := make([]string, len(x.Properties)) + for i, property := range x.Properties { + propertyStrings[i] = property.String() + } + return fmt.Sprintf("@%s-%s{%s}", x.LBracePos, x.RBracePos, + strings.Join(propertyStrings, ", ")) +} + +func (x *Map) Type() Type { return MapType } + +type List struct { + LBracePos scanner.Position + RBracePos scanner.Position + Values []Expression +} + +func (x *List) Pos() scanner.Position { return x.LBracePos } +func (x *List) End() scanner.Position { return x.RBracePos } + +func (x *List) Copy() Expression { + ret := *x + ret.Values = make([]Expression, len(x.Values)) + for i := range ret.Values { + ret.Values[i] = x.Values[i].Copy() + } + return &ret +} + +func (x *List) Eval() Expression { + return x +} + +func (x *List) String() string { + valueStrings := make([]string, len(x.Values)) + for i, value := range x.Values { + valueStrings[i] = value.String() + } + return fmt.Sprintf("@%s-%s[%s]", x.LBracePos, x.RBracePos, + strings.Join(valueStrings, ", ")) +} + +func (x *List) Type() Type { return ListType } + +type String struct { + LiteralPos scanner.Position + Value string +} + +func (x *String) Pos() scanner.Position { return x.LiteralPos } +func (x *String) End() scanner.Position { return x.LiteralPos } + +func (x *String) Copy() Expression { + ret := *x + return &ret +} + +func (x *String) Eval() Expression { + return x +} + +func (x *String) String() string { + return fmt.Sprintf("%q@%s", x.Value, x.LiteralPos) +} + +func (x *String) Type() Type { + return StringType +} + +type Bool struct { + LiteralPos scanner.Position + Value bool +} + +func (x *Bool) Pos() scanner.Position { return x.LiteralPos } +func (x *Bool) End() scanner.Position { return x.LiteralPos } + +func (x *Bool) Copy() Expression { + ret := *x + return &ret +} + +func (x *Bool) Eval() Expression { + return x +} + +func (x *Bool) String() string { + return fmt.Sprintf("%t@%s", x.Value, x.LiteralPos) +} + +func (x *Bool) Type() Type { + return BoolType +} + +type Comment struct { + Comment []string + Slash scanner.Position +} + +func (c Comment) Pos() scanner.Position { + return c.Slash +} + +func (c Comment) End() scanner.Position { + pos := c.Slash + for _, comment := range c.Comment { + pos.Offset += len(comment) + } + pos.Line += len(c.Comment) - 1 + return pos +} + +func (c Comment) String() string { + l := 0 + for _, comment := range c.Comment { + l += len(comment) + 1 + } + buf := make([]byte, 0, l) + for _, comment := range c.Comment { + buf = append(buf, comment...) + buf = append(buf, '\n') + } + + return string(buf) + "@" + c.Slash.String() +} + +// Return the text of the comment with // or /* and */ stripped +func (c Comment) Text() string { + l := 0 + for _, comment := range c.Comment { + l += len(comment) + 1 + } + buf := make([]byte, 0, l) + + blockComment := false + if strings.HasPrefix(c.Comment[0], "/*") { + blockComment = true + } + + for i, comment := range c.Comment { + if blockComment { + if i == 0 { + comment = strings.TrimPrefix(comment, "/*") + } + if i == len(c.Comment)-1 { + comment = strings.TrimSuffix(comment, "*/") + } + } else { + comment = strings.TrimPrefix(comment, "//") + } + buf = append(buf, comment...) + buf = append(buf, '\n') + } + + return string(buf) +} diff --git a/parser/modify.go b/parser/modify.go index 1b11e2c..08a3f3f 100644 --- a/parser/modify.go +++ b/parser/modify.go @@ -14,47 +14,38 @@ package parser -func AddStringToList(value *Value, s string) (modified bool) { - if value.Type != List { - panic("expected list value, got " + value.Type.String()) - } +import "fmt" - for _, v := range value.ListValue { - if v.Type != String { - panic("expected string in list, got " + value.Type.String()) +func AddStringToList(list *List, s string) (modified bool) { + for _, v := range list.Values { + if v.Type() != StringType { + panic(fmt.Errorf("expected string in list, got %s", v.Type())) } - if v.StringValue == s { + if sv, ok := v.(*String); ok && sv.Value == s { // string already exists return false } - } - value.ListValue = append(value.ListValue, Value{ - Type: String, - Pos: value.EndPos, - StringValue: s, + list.Values = append(list.Values, &String{ + LiteralPos: list.RBracePos, + Value: s, }) return true } -func RemoveStringFromList(value *Value, s string) (modified bool) { - if value.Type != List { - panic("expected list value, got " + value.Type.String()) - } - - for i, v := range value.ListValue { - if v.Type != String { - panic("expected string in list, got " + value.Type.String()) +func RemoveStringFromList(list *List, s string) (modified bool) { + for i, v := range list.Values { + if v.Type() != StringType { + panic(fmt.Errorf("expected string in list, got %s", v.Type())) } - if v.StringValue == s { - value.ListValue = append(value.ListValue[:i], value.ListValue[i+1:]...) + if sv, ok := v.(*String); ok && sv.Value == s { + list.Values = append(list.Values[:i], list.Values[i+1:]...) return true } - } return false diff --git a/parser/parser.go b/parser/parser.go index fb931af..6909c50 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -41,6 +41,7 @@ type File struct { Name string Defs []Definition Comments []Comment + Lines []scanner.Position } func parse(p *parser) (file *File, errs []error) { @@ -178,8 +179,8 @@ func (p *parser) parseDefinitions() (defs []Definition) { } } -func (p *parser) parseAssignment(name string, - namePos scanner.Position, assigner string) (assignment *Assignment) { +func (p *parser) parseAssignment(name string, namePos scanner.Position, + assigner string) (assignment *Assignment) { assignment = new(Assignment) @@ -223,10 +224,8 @@ func (p *parser) parseAssignment(name string, return } -func (p *parser) parseModule(typ string, - typPos scanner.Position) (module *Module) { +func (p *parser) parseModule(typ string, typPos scanner.Position) *Module { - module = new(Module) compat := false lbracePos := p.scanner.Position if p.tok == '{' { @@ -234,7 +233,7 @@ func (p *parser) parseModule(typ string, } if !p.accept(p.tok) { - return + return nil } properties := p.parsePropertyList(true, compat) rbracePos := p.scanner.Position @@ -244,11 +243,14 @@ func (p *parser) parseModule(typ string, p.accept('}') } - module.Type = Ident{typ, typPos} - module.Properties = properties - module.LbracePos = lbracePos - module.RbracePos = rbracePos - return + return &Module{ + Type: Ident{typ, typPos}, + Map: Map{ + Properties: properties, + LBracePos: lbracePos, + RBracePos: rbracePos, + }, + } } func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) { @@ -298,7 +300,7 @@ func (p *parser) parseProperty(isModule, compat bool) (property *Property) { return } -func (p *parser) parseExpression() (value Value) { +func (p *parser) parseExpression() (value Expression) { value = p.parseValue() switch p.tok { case '+': @@ -308,50 +310,48 @@ func (p *parser) parseExpression() (value Value) { } } -func (p *parser) evaluateOperator(value1, value2 Value, operator rune, - pos scanner.Position) (Value, error) { +func (p *parser) evaluateOperator(value1, value2 Expression, operator rune, + pos scanner.Position) (*Operator, error) { - value := Value{} + value := value1 if p.eval { - if value1.Type != value2.Type { - return Value{}, fmt.Errorf("mismatched type in operator %c: %s != %s", operator, - value1.Type, value2.Type) + e1 := value1.Eval() + e2 := value2.Eval() + if e1.Type() != e2.Type() { + return nil, fmt.Errorf("mismatched type in operator %c: %s != %s", operator, + e1.Type(), e2.Type()) } - value = value1 - value.Variable = "" + value = e1.Copy() 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: + switch v := value.(type) { + case *String: + v.Value += e2.(*String).Value + case *List: + v.Values = append(v.Values, e2.(*List).Values...) + case *Map: var err error - value.MapValue, err = p.addMaps(value.MapValue, value2.MapValue, pos) + v.Properties, err = p.addMaps(v.Properties, e2.(*Map).Properties, pos) if err != nil { - return Value{}, err + return nil, err } default: - return Value{}, fmt.Errorf("operator %c not supported on type %s", operator, - value1.Type) + return nil, fmt.Errorf("operator %c not supported on type %s", operator, v.Type()) } default: panic("unknown operator " + string(operator)) } } - value.Expression = &Expression{ - Args: [2]Value{value1, value2}, - Operator: operator, - Pos: pos, - } - - return value, nil + return &Operator{ + Args: [2]Expression{value1, value2}, + Operator: operator, + OperatorPos: pos, + Value: value, + }, nil } func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) { @@ -395,7 +395,7 @@ func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Prope return ret, nil } -func (p *parser) parseOperator(value1 Value) Value { +func (p *parser) parseOperator(value1 Expression) *Operator { operator := p.tok pos := p.scanner.Position p.accept(operator) @@ -405,13 +405,14 @@ func (p *parser) parseOperator(value1 Value) Value { value, err := p.evaluateOperator(value1, value2, operator, pos) if err != nil { p.error(err) - return Value{} + return nil } return value + } -func (p *parser) parseValue() (value Value) { +func (p *parser) parseValue() (value Expression) { switch p.tok { case scanner.Ident: return p.parseVariable() @@ -428,19 +429,19 @@ func (p *parser) parseValue() (value Value) { } } -func (p *parser) parseVariable() (value Value) { +func (p *parser) parseVariable() Expression { + var value Expression + switch text := p.scanner.TokenText(); text { - case "true": - value.Type = Bool - value.BoolValue = true - case "false": - value.Type = Bool - value.BoolValue = false + case "true", "false": + value = &Bool{ + LiteralPos: p.scanner.Position, + Value: text == "true", + } default: - variable := p.scanner.TokenText() if p.eval { - if assignment, local := p.scope.Get(variable); assignment == nil { - p.errorf("variable %q is not set", variable) + if assignment, local := p.scope.Get(text); assignment == nil { + p.errorf("variable %q is not set", text) } else { if local { assignment.Referenced = true @@ -448,40 +449,44 @@ func (p *parser) parseVariable() (value Value) { value = assignment.Value } } - value.Variable = variable + value = &Variable{ + Name: text, + NamePos: p.scanner.Position, + Value: value, + } } - value.Pos = p.scanner.Position p.accept(scanner.Ident) - return + return value } -func (p *parser) parseStringValue() (value Value) { - value.Type = String - value.Pos = p.scanner.Position +func (p *parser) parseStringValue() *String { str, err := strconv.Unquote(p.scanner.TokenText()) if err != nil { p.errorf("couldn't parse string: %s", err) - return + return nil + } + + value := &String{ + LiteralPos: p.scanner.Position, + Value: str, } - value.StringValue = str p.accept(scanner.String) - return + return value } -func (p *parser) parseListValue() (value Value) { - value.Type = List - value.Pos = p.scanner.Position +func (p *parser) parseListValue() *List { + lBracePos := p.scanner.Position if !p.accept('[') { - return + return nil } - var elements []Value + var elements []Expression for p.tok != ']' { element := p.parseExpression() - if p.eval && element.Type != String { - p.errorf("Expected string in list, found %s", element.String()) - return + if p.eval && element.Type() != StringType { + p.errorf("Expected string in list, found %s", element.Type().String()) + return nil } elements = append(elements, element) @@ -493,208 +498,32 @@ func (p *parser) parseListValue() (value Value) { p.accept(',') } - value.ListValue = elements - value.EndPos = p.scanner.Position - + rBracePos := p.scanner.Position p.accept(']') - return + + return &List{ + LBracePos: lBracePos, + RBracePos: rBracePos, + Values: elements, + } } -func (p *parser) parseMapValue() (value Value) { - value.Type = Map - value.Pos = p.scanner.Position +func (p *parser) parseMapValue() *Map { + lBracePos := p.scanner.Position if !p.accept('{') { - return + return nil } properties := p.parsePropertyList(false, false) - value.MapValue = properties - value.EndPos = p.scanner.Position + rBracePos := p.scanner.Position p.accept('}') - return -} - -type Expression struct { - Args [2]Value - Operator rune - Pos scanner.Position -} - -func (e *Expression) Copy() *Expression { - ret := *e - ret.Args[0] = e.Args[0].Copy() - ret.Args[1] = e.Args[1].Copy() - return &ret -} - -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 ( - Bool ValueType = iota - String - List - Map -) - -func (p ValueType) String() string { - switch p { - case Bool: - return "bool" - case String: - return "string" - case List: - return "list" - case Map: - return "map" - default: - panic(fmt.Errorf("unknown value type: %d", p)) - } -} - -type Definition interface { - String() string - definitionTag() -} - -type Assignment struct { - 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 %s", a.Name, a.Pos.Offset, a.Pos, a.Assigner, a.Value) -} - -func (a *Assignment) definitionTag() {} - -type Module struct { - Type Ident - Properties []*Property - LbracePos scanner.Position - RbracePos scanner.Position -} - -func (m *Module) Copy() *Module { - ret := *m - ret.Properties = make([]*Property, len(m.Properties)) - for i := range m.Properties { - ret.Properties[i] = m.Properties[i].Copy() - } - return &ret -} -func (m *Module) String() string { - propertyStrings := make([]string, len(m.Properties)) - for i, property := range m.Properties { - propertyStrings[i] = property.String() + return &Map{ + LBracePos: lBracePos, + RBracePos: rBracePos, + Properties: properties, } - return fmt.Sprintf("%s@%d:%s-%d:%s{%s}", m.Type, - m.LbracePos.Offset, m.LbracePos, - m.RbracePos.Offset, m.RbracePos, - strings.Join(propertyStrings, ", ")) -} - -func (m *Module) definitionTag() {} - -type Property struct { - Name Ident - Value Value - Pos scanner.Position -} - -func (p *Property) Copy() *Property { - ret := *p - ret.Value = p.Value.Copy() - return &ret -} - -func (p *Property) String() string { - return fmt.Sprintf("%s@%d:%s: %s", p.Name, p.Pos.Offset, p.Pos, p.Value) -} - -type Ident struct { - Name string - Pos scanner.Position -} - -func (i Ident) String() string { - return fmt.Sprintf("%s@%d:%s", i.Name, i.Pos.Offset, i.Pos) -} - -type Value struct { - Type ValueType - BoolValue bool - StringValue string - ListValue []Value - MapValue []*Property - Expression *Expression - Variable string - Pos scanner.Position - EndPos scanner.Position -} - -func (p Value) Copy() Value { - ret := p - if p.MapValue != nil { - ret.MapValue = make([]*Property, len(p.MapValue)) - for i := range p.MapValue { - ret.MapValue[i] = p.MapValue[i].Copy() - } - } - if p.ListValue != nil { - ret.ListValue = make([]Value, len(p.ListValue)) - for i := range p.ListValue { - ret.ListValue[i] = p.ListValue[i].Copy() - } - } - if p.Expression != nil { - ret.Expression = p.Expression.Copy() - } - return ret -} - -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: - s += fmt.Sprintf("%t@%d:%s", p.BoolValue, p.Pos.Offset, p.Pos) - case String: - 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() - } - 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() - } - 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 { @@ -774,58 +603,3 @@ func (s *Scope) String() string { return strings.Join(ret, "\n") } - -type Comment struct { - Comment []string - Pos scanner.Position -} - -func (c Comment) String() string { - l := 0 - for _, comment := range c.Comment { - l += len(comment) + 1 - } - buf := make([]byte, 0, l) - for _, comment := range c.Comment { - buf = append(buf, comment...) - buf = append(buf, '\n') - } - - return string(buf) -} - -// Return the text of the comment with // or /* and */ stripped -func (c Comment) Text() string { - l := 0 - for _, comment := range c.Comment { - l += len(comment) + 1 - } - buf := make([]byte, 0, l) - - blockComment := false - if strings.HasPrefix(c.Comment[0], "/*") { - blockComment = true - } - - for i, comment := range c.Comment { - if blockComment { - if i == 0 { - comment = strings.TrimPrefix(comment, "/*") - } - if i == len(c.Comment)-1 { - comment = strings.TrimSuffix(comment, "*/") - } - } else { - comment = strings.TrimPrefix(comment, "//") - } - buf = append(buf, comment...) - buf = append(buf, '\n') - } - - return string(buf) -} - -// Return the line number that the comment ends on -func (c Comment) EndLine() int { - return c.Pos.Line + len(c.Comment) - 1 -} diff --git a/parser/parser_test.go b/parser/parser_test.go index 8925684..e93bb09 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -39,9 +39,11 @@ var validParseTestCases = []struct { `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(3, 2, 3)}, - LbracePos: mkpos(7, 2, 7), - RbracePos: mkpos(8, 2, 8), + Type: Ident{"foo", mkpos(3, 2, 3)}, + Map: Map{ + LBracePos: mkpos(7, 2, 7), + RBracePos: mkpos(8, 2, 8), + }, }, }, nil, @@ -54,17 +56,18 @@ var validParseTestCases = []struct { `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(3, 2, 3)}, - LbracePos: mkpos(7, 2, 7), - RbracePos: mkpos(27, 4, 3), - Properties: []*Property{ - { - Name: Ident{"name", mkpos(12, 3, 4)}, - Pos: mkpos(16, 3, 8), - Value: Value{ - Type: String, - Pos: mkpos(18, 3, 10), - StringValue: "abc", + Type: Ident{"foo", mkpos(3, 2, 3)}, + Map: Map{ + LBracePos: mkpos(7, 2, 7), + RBracePos: mkpos(27, 4, 3), + Properties: []*Property{ + { + Name: Ident{"name", mkpos(12, 3, 4)}, + Pos: mkpos(16, 3, 8), + Value: &String{ + LiteralPos: mkpos(18, 3, 10), + Value: "abc", + }, }, }, }, @@ -80,17 +83,18 @@ var validParseTestCases = []struct { `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(3, 2, 3)}, - LbracePos: mkpos(7, 2, 7), - RbracePos: mkpos(28, 4, 3), - Properties: []*Property{ - { - Name: Ident{"isGood", mkpos(12, 3, 4)}, - Pos: mkpos(18, 3, 10), - Value: Value{ - Type: Bool, - Pos: mkpos(20, 3, 12), - BoolValue: true, + Type: Ident{"foo", mkpos(3, 2, 3)}, + Map: Map{ + LBracePos: mkpos(7, 2, 7), + RBracePos: mkpos(28, 4, 3), + Properties: []*Property{ + { + Name: Ident{"isGood", mkpos(12, 3, 4)}, + Pos: mkpos(18, 3, 10), + Value: &Bool{ + LiteralPos: mkpos(20, 3, 12), + Value: true, + }, }, }, }, @@ -107,42 +111,38 @@ var validParseTestCases = []struct { `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(3, 2, 3)}, - LbracePos: mkpos(7, 2, 7), - RbracePos: mkpos(67, 5, 3), - Properties: []*Property{ - { - Name: Ident{"stuff", mkpos(12, 3, 4)}, - Pos: mkpos(17, 3, 9), - Value: Value{ - Type: List, - Pos: mkpos(19, 3, 11), - EndPos: mkpos(63, 4, 19), - ListValue: []Value{ - Value{ - Type: String, - Pos: mkpos(20, 3, 12), - StringValue: "asdf", - }, - Value{ - Type: String, - Pos: mkpos(28, 3, 20), - StringValue: "jkl;", - }, - Value{ - Type: String, - Pos: mkpos(36, 3, 28), - StringValue: "qwert", - }, - Value{ - Type: String, - Pos: mkpos(49, 4, 5), - StringValue: "uiop", - }, - Value{ - Type: String, - Pos: mkpos(57, 4, 13), - StringValue: "bnm,", + Type: Ident{"foo", mkpos(3, 2, 3)}, + Map: Map{ + LBracePos: mkpos(7, 2, 7), + RBracePos: mkpos(67, 5, 3), + Properties: []*Property{ + { + Name: Ident{"stuff", mkpos(12, 3, 4)}, + Pos: mkpos(17, 3, 9), + Value: &List{ + LBracePos: mkpos(19, 3, 11), + RBracePos: mkpos(63, 4, 19), + Values: []Expression{ + &String{ + LiteralPos: mkpos(20, 3, 12), + Value: "asdf", + }, + &String{ + LiteralPos: mkpos(28, 3, 20), + Value: "jkl;", + }, + &String{ + LiteralPos: mkpos(36, 3, 28), + Value: "qwert", + }, + &String{ + LiteralPos: mkpos(49, 4, 5), + Value: "uiop", + }, + &String{ + LiteralPos: mkpos(57, 4, 13), + Value: "bnm,", + }, }, }, }, @@ -163,34 +163,33 @@ var validParseTestCases = []struct { `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(3, 2, 3)}, - LbracePos: mkpos(7, 2, 7), - RbracePos: mkpos(62, 7, 3), - Properties: []*Property{ - { - Name: Ident{"stuff", mkpos(12, 3, 4)}, - Pos: mkpos(17, 3, 9), - Value: Value{ - Type: Map, - Pos: mkpos(19, 3, 11), - EndPos: mkpos(58, 6, 4), - MapValue: []*Property{ - { - Name: Ident{"isGood", mkpos(25, 4, 5)}, - Pos: mkpos(31, 4, 11), - Value: Value{ - Type: Bool, - Pos: mkpos(33, 4, 13), - BoolValue: true, + Type: Ident{"foo", mkpos(3, 2, 3)}, + Map: Map{ + LBracePos: mkpos(7, 2, 7), + RBracePos: mkpos(62, 7, 3), + Properties: []*Property{ + { + Name: Ident{"stuff", mkpos(12, 3, 4)}, + Pos: mkpos(17, 3, 9), + Value: &Map{ + LBracePos: mkpos(19, 3, 11), + RBracePos: mkpos(58, 6, 4), + Properties: []*Property{ + { + Name: Ident{"isGood", mkpos(25, 4, 5)}, + Pos: mkpos(31, 4, 11), + Value: &Bool{ + LiteralPos: mkpos(33, 4, 13), + Value: true, + }, }, - }, - { - Name: Ident{"name", mkpos(43, 5, 5)}, - Pos: mkpos(47, 5, 9), - Value: Value{ - Type: String, - Pos: mkpos(49, 5, 11), - StringValue: "bar", + { + Name: Ident{"name", mkpos(43, 5, 5)}, + Pos: mkpos(47, 5, 9), + Value: &String{ + LiteralPos: mkpos(49, 5, 11), + Value: "bar", + }, }, }, }, @@ -204,24 +203,25 @@ var validParseTestCases = []struct { {` // comment1 - foo { + foo /* test */ { // comment2 isGood: true, // comment3 } `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(17, 3, 3)}, - LbracePos: mkpos(21, 3, 7), - RbracePos: mkpos(70, 6, 3), - Properties: []*Property{ - { - Name: Ident{"isGood", mkpos(41, 5, 4)}, - Pos: mkpos(47, 5, 10), - Value: Value{ - Type: Bool, - Pos: mkpos(49, 5, 12), - BoolValue: true, + Type: Ident{"foo", mkpos(17, 3, 3)}, + Map: Map{ + LBracePos: mkpos(32, 3, 18), + RBracePos: mkpos(81, 6, 3), + Properties: []*Property{ + { + Name: Ident{"isGood", mkpos(52, 5, 4)}, + Pos: mkpos(58, 5, 10), + Value: &Bool{ + LiteralPos: mkpos(60, 5, 12), + Value: true, + }, }, }, }, @@ -230,15 +230,19 @@ var validParseTestCases = []struct { []Comment{ Comment{ Comment: []string{"// comment1"}, - Pos: mkpos(3, 2, 3), + Slash: mkpos(3, 2, 3), + }, + Comment{ + Comment: []string{"/* test */"}, + Slash: mkpos(21, 3, 7), }, Comment{ Comment: []string{"// comment2"}, - Pos: mkpos(26, 4, 4), + Slash: mkpos(37, 4, 4), }, Comment{ Comment: []string{"// comment3"}, - Pos: mkpos(56, 5, 19), + Slash: mkpos(67, 5, 19), }, }, }, @@ -254,33 +258,35 @@ var validParseTestCases = []struct { `, []Definition{ &Module{ - Type: Ident{"foo", mkpos(3, 2, 3)}, - LbracePos: mkpos(7, 2, 7), - RbracePos: mkpos(27, 4, 3), - Properties: []*Property{ - { - Name: Ident{"name", mkpos(12, 3, 4)}, - Pos: mkpos(16, 3, 8), - Value: Value{ - Type: String, - Pos: mkpos(18, 3, 10), - StringValue: "abc", + Type: Ident{"foo", mkpos(3, 2, 3)}, + Map: Map{ + LBracePos: mkpos(7, 2, 7), + RBracePos: mkpos(27, 4, 3), + Properties: []*Property{ + { + Name: Ident{"name", mkpos(12, 3, 4)}, + Pos: mkpos(16, 3, 8), + Value: &String{ + LiteralPos: mkpos(18, 3, 10), + Value: "abc", + }, }, }, }, }, &Module{ - Type: Ident{"bar", mkpos(32, 6, 3)}, - LbracePos: mkpos(36, 6, 7), - RbracePos: mkpos(56, 8, 3), - Properties: []*Property{ - { - Name: Ident{"name", mkpos(41, 7, 4)}, - Pos: mkpos(45, 7, 8), - Value: Value{ - Type: String, - Pos: mkpos(47, 7, 10), - StringValue: "def", + Type: Ident{"bar", mkpos(32, 6, 3)}, + Map: Map{ + LBracePos: mkpos(36, 6, 7), + RBracePos: mkpos(56, 8, 3), + Properties: []*Property{ + { + Name: Ident{"name", mkpos(41, 7, 4)}, + Pos: mkpos(45, 7, 8), + Value: &String{ + LiteralPos: mkpos(47, 7, 10), + Value: "def", + }, }, }, }, @@ -299,15 +305,13 @@ var validParseTestCases = []struct { &Assignment{ Name: Ident{"foo", mkpos(3, 2, 3)}, Pos: mkpos(7, 2, 7), - Value: Value{ - Type: String, - Pos: mkpos(9, 2, 9), - StringValue: "stuff", + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", }, - OrigValue: Value{ - Type: String, - Pos: mkpos(9, 2, 9), - StringValue: "stuff", + OrigValue: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", }, Assigner: "=", Referenced: true, @@ -315,17 +319,21 @@ var validParseTestCases = []struct { &Assignment{ Name: Ident{"bar", mkpos(19, 3, 3)}, Pos: mkpos(23, 3, 7), - Value: Value{ - Type: String, - Pos: mkpos(25, 3, 9), - StringValue: "stuff", - Variable: "foo", + Value: &Variable{ + Name: "foo", + NamePos: mkpos(25, 3, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, - OrigValue: Value{ - Type: String, - Pos: mkpos(25, 3, 9), - StringValue: "stuff", - Variable: "foo", + OrigValue: &Variable{ + Name: "foo", + NamePos: mkpos(25, 3, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, Assigner: "=", Referenced: true, @@ -333,50 +341,64 @@ var validParseTestCases = []struct { &Assignment{ Name: Ident{"baz", mkpos(31, 4, 3)}, Pos: mkpos(35, 4, 7), - Value: 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", + Value: &Operator{ + OperatorPos: mkpos(41, 4, 13), + Operator: '+', + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuffstuff", + }, + Args: [2]Expression{ + &Variable{ + Name: "foo", + NamePos: mkpos(37, 4, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", }, - { - Type: String, - Pos: mkpos(43, 4, 15), - StringValue: "stuff", - Variable: "bar", + }, + &Variable{ + Name: "bar", + NamePos: mkpos(43, 4, 15), + Value: &Variable{ + Name: "foo", + NamePos: mkpos(25, 3, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, }, - Operator: '+', - 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", + OrigValue: &Operator{ + OperatorPos: mkpos(41, 4, 13), + Operator: '+', + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuffstuff", + }, + Args: [2]Expression{ + &Variable{ + Name: "foo", + NamePos: mkpos(37, 4, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", }, - { - Type: String, - Pos: mkpos(43, 4, 15), - StringValue: "stuff", - Variable: "bar", + }, + &Variable{ + Name: "bar", + NamePos: mkpos(43, 4, 15), + Value: &Variable{ + Name: "foo", + NamePos: mkpos(25, 3, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, }, - Operator: '+', - Pos: mkpos(41, 4, 13), }, }, Assigner: "=", @@ -385,69 +407,90 @@ var validParseTestCases = []struct { &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", + Value: &Operator{ + Args: [2]Expression{ + &Variable{ + Name: "baz", + NamePos: mkpos(55, 5, 9), + Value: &Operator{ + OperatorPos: mkpos(41, 4, 13), + Operator: '+', + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuffstuff", + }, + Args: [2]Expression{ + &Variable{ + Name: "foo", + NamePos: mkpos(37, 4, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", }, - { - Type: String, - Pos: mkpos(43, 4, 15), - StringValue: "stuff", - Variable: "bar", + }, + &Variable{ + Name: "bar", + NamePos: mkpos(43, 4, 15), + Value: &Variable{ + Name: "foo", + NamePos: mkpos(25, 3, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, }, - Operator: '+', - Pos: mkpos(41, 4, 13), }, }, - { - Variable: "foo", - Type: String, - Pos: mkpos(68, 6, 10), - StringValue: "stuff", + }, + &Variable{ + Name: "foo", + NamePos: mkpos(68, 6, 10), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", }, }, - Pos: mkpos(66, 6, 8), - Operator: '+', + }, + OperatorPos: mkpos(66, 6, 8), + Operator: '+', + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuffstuffstuff", }, }, - 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", + OrigValue: &Variable{ + Name: "baz", + NamePos: mkpos(55, 5, 9), + Value: &Operator{ + OperatorPos: mkpos(41, 4, 13), + Operator: '+', + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuffstuff", + }, + Args: [2]Expression{ + &Variable{ + Name: "foo", + NamePos: mkpos(37, 4, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, - { - Type: String, - Pos: mkpos(43, 4, 15), - StringValue: "stuff", - Variable: "bar", + &Variable{ + Name: "bar", + NamePos: mkpos(43, 4, 15), + Value: &Variable{ + Name: "foo", + NamePos: mkpos(25, 3, 9), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, + }, }, }, - Operator: '+', - Pos: mkpos(41, 4, 13), }, }, Assigner: "=", @@ -455,17 +498,21 @@ var validParseTestCases = []struct { &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", + Value: &Variable{ + Name: "foo", + NamePos: mkpos(68, 6, 10), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, - OrigValue: Value{ - Type: String, - Pos: mkpos(68, 6, 10), - StringValue: "stuff", - Variable: "foo", + OrigValue: &Variable{ + Name: "foo", + NamePos: mkpos(68, 6, 10), + Value: &String{ + LiteralPos: mkpos(9, 2, 9), + Value: "stuff", + }, }, Assigner: "+=", }, @@ -504,7 +551,7 @@ func TestParseValidInput(t *testing.T) { if len(file.Comments) == len(testCase.comments) { for i := range file.Comments { - if !reflect.DeepEqual(file.Comments, testCase.comments) { + if !reflect.DeepEqual(file.Comments[i], testCase.comments[i]) { t.Errorf("test case: %s", testCase.input) t.Errorf("incorrect comment %d:", i) t.Errorf(" expected: %s", testCase.comments[i]) diff --git a/parser/printer.go b/parser/printer.go index b27f5e0..1e7bc2a 100644 --- a/parser/printer.go +++ b/parser/printer.go @@ -22,7 +22,7 @@ import ( "unicode" ) -var noPos = scanner.Position{} +var noPos scanner.Position type printer struct { defs []Definition @@ -91,96 +91,94 @@ func (p *printer) printAssignment(assignment *Assignment) { p.requestSpace() p.printToken(assignment.Assigner, assignment.Pos) p.requestSpace() - p.printValue(assignment.OrigValue) + p.printExpression(assignment.OrigValue) p.requestNewline() } func (p *printer) printModule(module *Module) { p.printToken(module.Type.Name, module.Type.Pos) - p.printMap(module.Properties, module.LbracePos, module.RbracePos) + p.printMap(&module.Map) p.requestDoubleNewline() } -func (p *printer) printValue(value Value) { - if value.Variable != "" { - p.printToken(value.Variable, value.Pos) - } else if value.Expression != nil { - p.printExpression(*value.Expression) - } else { - switch value.Type { - case Bool: - var s string - if value.BoolValue { - s = "true" - } else { - s = "false" - } - p.printToken(s, value.Pos) - case String: - p.printToken(strconv.Quote(value.StringValue), value.Pos) - case List: - p.printList(value.ListValue, value.Pos, value.EndPos) - case Map: - p.printMap(value.MapValue, value.Pos, value.EndPos) - default: - panic(fmt.Errorf("bad property type: %d", value.Type)) +func (p *printer) printExpression(value Expression) { + switch v := value.(type) { + case *Variable: + p.printToken(v.Name, v.NamePos) + case *Operator: + p.printOperator(v) + case *Bool: + var s string + if v.Value { + s = "true" + } else { + s = "false" } + p.printToken(s, v.LiteralPos) + case *String: + p.printToken(strconv.Quote(v.Value), v.LiteralPos) + case *List: + p.printList(v.Values, v.LBracePos, v.RBracePos) + case *Map: + p.printMap(v) + default: + panic(fmt.Errorf("bad property type: %d", value.Type)) } } -func (p *printer) printList(list []Value, pos, endPos scanner.Position) { +func (p *printer) printList(list []Expression, pos, endPos scanner.Position) { p.requestSpace() p.printToken("[", pos) if len(list) > 1 || pos.Line != endPos.Line { p.requestNewline() p.indent(p.curIndent() + 4) for _, value := range list { - p.printValue(value) + p.printExpression(value) p.printToken(",", noPos) p.requestNewline() } p.unindent(endPos) } else { for _, value := range list { - p.printValue(value) + p.printExpression(value) } } p.printToken("]", endPos) } -func (p *printer) printMap(list []*Property, pos, endPos scanner.Position) { +func (p *printer) printMap(m *Map) { p.requestSpace() - p.printToken("{", pos) - if len(list) > 0 || pos.Line != endPos.Line { + p.printToken("{", m.LBracePos) + if len(m.Properties) > 0 || m.LBracePos.Line != m.RBracePos.Line { p.requestNewline() p.indent(p.curIndent() + 4) - for _, prop := range list { + for _, prop := range m.Properties { p.printProperty(prop) p.printToken(",", noPos) p.requestNewline() } - p.unindent(endPos) + p.unindent(m.RBracePos) } - p.printToken("}", endPos) + p.printToken("}", m.RBracePos) } -func (p *printer) printExpression(expression Expression) { - p.printValue(expression.Args[0]) +func (p *printer) printOperator(operator *Operator) { + p.printExpression(operator.Args[0]) p.requestSpace() - p.printToken(string(expression.Operator), expression.Pos) - if expression.Args[0].Pos.Line == expression.Args[1].Pos.Line { + p.printToken(string(operator.Operator), operator.OperatorPos) + if operator.Args[0].End().Line == operator.Args[1].Pos().Line { p.requestSpace() } else { p.requestNewline() } - p.printValue(expression.Args[1]) + p.printExpression(operator.Args[1]) } func (p *printer) printProperty(property *Property) { p.printToken(property.Name.Name, property.Name.Pos) p.printToken(":", property.Pos) p.requestSpace() - p.printValue(property.Value) + p.printExpression(property.Value) } // Print a single token, including any necessary comments or whitespace between @@ -208,7 +206,7 @@ func (p *printer) printToken(s string, pos scanner.Position) { // Print any in-line (single line /* */) comments that appear _before_ pos func (p *printer) printInLineCommentsBefore(pos scanner.Position) { - for p.curComment < len(p.comments) && p.comments[p.curComment].Pos.Offset < pos.Offset { + for p.curComment < len(p.comments) && p.comments[p.curComment].Slash.Offset < pos.Offset { c := p.comments[p.curComment] if c.Comment[0][0:2] == "//" || len(c.Comment) > 1 { p.skippedComments = append(p.skippedComments, c) @@ -225,16 +223,16 @@ func (p *printer) printInLineCommentsBefore(pos scanner.Position) { // by pos func (p *printer) printEndOfLineCommentsBefore(pos scanner.Position) { for _, c := range p.skippedComments { - if !p.requestNewlinesForPos(c.Pos) { + if !p.requestNewlinesForPos(c.Slash) { p.requestSpace() } p.printComment(c) p._requestNewline() } p.skippedComments = []Comment{} - for p.curComment < len(p.comments) && p.comments[p.curComment].Pos.Line < pos.Line { + for p.curComment < len(p.comments) && p.comments[p.curComment].Slash.Line < pos.Line { c := p.comments[p.curComment] - if !p.requestNewlinesForPos(c.Pos) { + if !p.requestNewlinesForPos(c.Slash) { p.requestSpace() } p.printComment(c) @@ -303,7 +301,7 @@ func (p *printer) flushSpace() { // Print a single comment, which may be a multi-line comment func (p *printer) printComment(comment Comment) { - pos := comment.Pos + pos := comment.Slash for i, line := range comment.Comment { line = strings.TrimRightFunc(line, unicode.IsSpace) p.flushSpace() @@ -324,14 +322,14 @@ func (p *printer) printComment(comment Comment) { // Print any comments that occur after the last token, and a trailing newline func (p *printer) flush() { for _, c := range p.skippedComments { - if !p.requestNewlinesForPos(c.Pos) { + if !p.requestNewlinesForPos(c.Slash) { p.requestSpace() } p.printComment(c) } for p.curComment < len(p.comments) { c := p.comments[p.curComment] - if !p.requestNewlinesForPos(c.Pos) { + if !p.requestNewlinesForPos(c.Slash) { p.requestSpace() } p.printComment(c) diff --git a/parser/printer_test.go b/parser/printer_test.go index e5acf06..3e759cb 100644 --- a/parser/printer_test.go +++ b/parser/printer_test.go @@ -62,6 +62,49 @@ foo { }, { input: ` + var = "asdf" + foo { + stuff: ["asdf"] + var, + }`, + output: ` +var = "asdf" +foo { + stuff: ["asdf"] + var, +} +`, + }, + { + input: ` + var = "asdf" + foo { + stuff: [ + "asdf" + ] + var, + }`, + output: ` +var = "asdf" +foo { + stuff: [ + "asdf", + ] + var, +} +`, + }, + { + input: ` + var = "asdf" + foo { + stuff: ["asdf"] + var + ["qwert"], + }`, + output: ` +var = "asdf" +foo { + stuff: ["asdf"] + var + ["qwert"], +} +`, + }, + { + input: ` foo { stuff: { isGood: true, diff --git a/parser/sort.go b/parser/sort.go index 381ef82..05ce5fd 100644 --- a/parser/sort.go +++ b/parser/sort.go @@ -32,40 +32,40 @@ func SortLists(file *File) { sort.Sort(commentsByOffset(file.Comments)) } -func SortList(file *File, value Value) { - for i := 0; i < len(value.ListValue); i++ { +func SortList(file *File, list *List) { + for i := 0; i < len(list.Values); i++ { // Find a set of values on contiguous lines - line := value.ListValue[i].Pos.Line + line := list.Values[i].Pos().Line var j int - for j = i + 1; j < len(value.ListValue); j++ { - if value.ListValue[j].Pos.Line > line+1 { + for j = i + 1; j < len(list.Values); j++ { + if list.Values[j].Pos().Line > line+1 { break } - line = value.ListValue[j].Pos.Line + line = list.Values[j].Pos().Line } - nextPos := value.EndPos - if j < len(value.ListValue) { - nextPos = value.ListValue[j].Pos + nextPos := list.End() + if j < len(list.Values) { + nextPos = list.Values[j].Pos() } - sortSubList(value.ListValue[i:j], nextPos, file) + sortSubList(list.Values[i:j], nextPos, file) i = j - 1 } } -func ListIsSorted(value Value) bool { - for i := 0; i < len(value.ListValue); i++ { +func ListIsSorted(list *List) bool { + for i := 0; i < len(list.Values); i++ { // Find a set of values on contiguous lines - line := value.ListValue[i].Pos.Line + line := list.Values[i].Pos().Line var j int - for j = i + 1; j < len(value.ListValue); j++ { - if value.ListValue[j].Pos.Line > line+1 { + for j = i + 1; j < len(list.Values); j++ { + if list.Values[j].Pos().Line > line+1 { break } - line = value.ListValue[j].Pos.Line + line = list.Values[j].Pos().Line } - if !subListIsSorted(value.ListValue[i:j]) { + if !subListIsSorted(list.Values[i:j]) { return false } i = j - 1 @@ -74,55 +74,49 @@ func ListIsSorted(value Value) bool { return true } -func sortListsInValue(value Value, file *File) { - if value.Variable != "" { - return - } - - if value.Expression != nil { - sortListsInValue(value.Expression.Args[0], file) - sortListsInValue(value.Expression.Args[1], file) - return - } - - if value.Type == Map { - for _, p := range value.MapValue { +func sortListsInValue(value Expression, file *File) { + switch v := value.(type) { + case *Variable: + // Nothing + case *Operator: + sortListsInValue(v.Args[0], file) + sortListsInValue(v.Args[1], file) + case *Map: + for _, p := range v.Properties { sortListsInValue(p.Value, file) } - return - } else if value.Type != List { - return + case *List: + SortList(file, v) } - - SortList(file, value) } -func sortSubList(values []Value, nextPos scanner.Position, file *File) { +func sortSubList(values []Expression, nextPos scanner.Position, file *File) { l := make(elemList, len(values)) for i, v := range values { - if v.Type != String { + s, ok := v.(*String) + if !ok { panic("list contains non-string element") } n := nextPos if i < len(values)-1 { - n = values[i+1].Pos + n = values[i+1].Pos() } - l[i] = elem{v.StringValue, i, v.Pos, n} + l[i] = elem{s.Value, i, v.Pos(), n} } sort.Sort(l) - copyValues := append([]Value{}, values...) + copyValues := append([]Expression{}, values...) copyComments := append([]Comment{}, file.Comments...) - curPos := values[0].Pos + curPos := values[0].Pos() for i, e := range l { values[i] = copyValues[e.i] - values[i].Pos = curPos + values[i].(*String).LiteralPos = curPos for j, c := range copyComments { - if c.Pos.Offset > e.pos.Offset && c.Pos.Offset < e.nextPos.Offset { - file.Comments[j].Pos.Line = curPos.Line - file.Comments[j].Pos.Offset += values[i].Pos.Offset - e.pos.Offset + if c.Pos().Offset > e.pos.Offset && c.Pos().Offset < e.nextPos.Offset { + file.Comments[j].Slash.Line = curPos.Line + file.Comments[j].Slash.Offset += values[i].Pos().Offset - e.pos.Offset } } @@ -131,16 +125,17 @@ func sortSubList(values []Value, nextPos scanner.Position, file *File) { } } -func subListIsSorted(values []Value) bool { +func subListIsSorted(values []Expression) bool { prev := "" for _, v := range values { - if v.Type != String { + s, ok := v.(*String) + if !ok { panic("list contains non-string element") } - if prev > v.StringValue { + if prev > s.Value { return false } - prev = v.StringValue + prev = s.Value } return true @@ -174,7 +169,7 @@ func (l commentsByOffset) Len() int { } func (l commentsByOffset) Less(i, j int) bool { - return l[i].Pos.Offset < l[j].Pos.Offset + return l[i].Pos().Offset < l[j].Pos().Offset } func (l commentsByOffset) Swap(i, j int) { @@ -88,7 +88,6 @@ func buildPropertyMap(namePrefix string, propertyDefs []*parser.Property, // We've already added this property. continue } - errs = append(errs, &Error{ Err: fmt.Errorf("property %q already defined", name), Pos: propertyDef.Pos, @@ -276,47 +275,49 @@ func unpackStructValue(namePrefix string, structValue reflect.Value, } func unpackBool(boolValue reflect.Value, property *parser.Property) []error { - if property.Value.Type != parser.Bool { + b, ok := property.Value.Eval().(*parser.Bool) + if !ok { return []error{ - fmt.Errorf("%s: can't assign %s value to %s property %q", - property.Value.Pos, property.Value.Type, parser.Bool, - property.Name), + fmt.Errorf("%s: can't assign %s value to bool property %q", + property.Value.Pos, property.Value.Type, property.Name), } } - boolValue.SetBool(property.Value.BoolValue) + boolValue.SetBool(b.Value) return nil } func unpackString(stringValue reflect.Value, property *parser.Property) []error { - if property.Value.Type != parser.String { + s, ok := property.Value.Eval().(*parser.String) + if !ok { return []error{ - fmt.Errorf("%s: can't assign %s value to %s property %q", - property.Value.Pos, property.Value.Type, parser.String, - property.Name), + fmt.Errorf("%s: can't assign %s value to string property %q", + property.Value.Pos, property.Value.Type, property.Name), } } - stringValue.SetString(property.Value.StringValue) + stringValue.SetString(s.Value) return nil } func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error { - if property.Value.Type != parser.List { + + l, ok := property.Value.Eval().(*parser.List) + if !ok { return []error{ - fmt.Errorf("%s: can't assign %s value to %s property %q", - property.Value.Pos, property.Value.Type, parser.List, - property.Name), + fmt.Errorf("%s: can't assign %s value to list property %q", + property.Value.Pos, property.Value.Type, property.Name), } } - list := []string{} - for _, value := range property.Value.ListValue { - if value.Type != parser.String { + list := make([]string, len(l.Values)) + for i, value := range l.Values { + s, ok := value.Eval().(*parser.String) + if !ok { // The parser should not produce this. - panic("non-string value found in list") + panic(fmt.Errorf("non-string value %q found in list", value)) } - list = append(list, value.StringValue) + list[i] = s.Value } sliceValue.Set(reflect.ValueOf(list)) @@ -327,15 +328,15 @@ func unpackStruct(namePrefix string, structValue reflect.Value, property *parser.Property, propertyMap map[string]*packedProperty, filterKey, filterValue string) []error { - if property.Value.Type != parser.Map { + m, ok := property.Value.Eval().(*parser.Map) + if !ok { return []error{ - fmt.Errorf("%s: can't assign %s value to %s property %q", - property.Value.Pos, property.Value.Type, parser.Map, - property.Name), + fmt.Errorf("%s: can't assign %s value to map property %q", + property.Value.Pos, property.Value.Type, property.Name), } } - errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap) + errs := buildPropertyMap(namePrefix, m.Properties, propertyMap) if len(errs) > 0 { return errs } diff --git a/unpack_test.go b/unpack_test.go index 3efd447..7b314dd 100644 --- a/unpack_test.go +++ b/unpack_test.go @@ -27,7 +27,7 @@ import ( var validUnpackTestCases = []struct { input string - output interface{} + output []interface{} errs []error }{ {` @@ -36,14 +36,16 @@ var validUnpackTestCases = []struct { blank: "", } `, - struct { - Name *string - Blank *string - Unset *string - }{ - Name: proptools.StringPtr("abc"), - Blank: proptools.StringPtr(""), - Unset: nil, + []interface{}{ + struct { + Name *string + Blank *string + Unset *string + }{ + Name: proptools.StringPtr("abc"), + Blank: proptools.StringPtr(""), + Unset: nil, + }, }, nil, }, @@ -53,10 +55,12 @@ var validUnpackTestCases = []struct { name: "abc", } `, - struct { - Name string - }{ - Name: "abc", + []interface{}{ + struct { + Name string + }{ + Name: "abc", + }, }, nil, }, @@ -66,10 +70,12 @@ var validUnpackTestCases = []struct { isGood: true, } `, - struct { - IsGood bool - }{ - IsGood: true, + []interface{}{ + struct { + IsGood bool + }{ + IsGood: true, + }, }, nil, }, @@ -80,14 +86,16 @@ var validUnpackTestCases = []struct { isBad: false, } `, - struct { - IsGood *bool - IsBad *bool - IsUgly *bool - }{ - IsGood: proptools.BoolPtr(true), - IsBad: proptools.BoolPtr(false), - IsUgly: nil, + []interface{}{ + struct { + IsGood *bool + IsBad *bool + IsUgly *bool + }{ + IsGood: proptools.BoolPtr(true), + IsBad: proptools.BoolPtr(false), + IsUgly: nil, + }, }, nil, }, @@ -99,14 +107,16 @@ var validUnpackTestCases = []struct { empty: [] } `, - struct { - Stuff []string - Empty []string - Nil []string - }{ - Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"}, - Empty: []string{}, - Nil: nil, + []interface{}{ + struct { + Stuff []string + Empty []string + Nil []string + }{ + Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"}, + Empty: []string{}, + Nil: nil, + }, }, nil, }, @@ -118,13 +128,15 @@ var validUnpackTestCases = []struct { } } `, - struct { - Nested struct { - Name string - } - }{ - Nested: struct{ Name string }{ - Name: "abc", + []interface{}{ + struct { + Nested struct { + Name string + } + }{ + Nested: struct{ Name string }{ + Name: "abc", + }, }, }, nil, @@ -137,11 +149,13 @@ var validUnpackTestCases = []struct { } } `, - struct { - Nested interface{} - }{ - Nested: &struct{ Name string }{ - Name: "def", + []interface{}{ + struct { + Nested interface{} + }{ + Nested: &struct{ Name string }{ + Name: "def", + }, }, }, nil, @@ -156,18 +170,20 @@ var validUnpackTestCases = []struct { baz: ["def", "ghi"], } `, - struct { - Nested struct { - Foo string - } - Bar bool - Baz []string - }{ - Nested: struct{ Foo string }{ - Foo: "abc", + []interface{}{ + struct { + Nested struct { + Foo string + } + Bar bool + Baz []string + }{ + Nested: struct{ Foo string }{ + Foo: "abc", + }, + Bar: false, + Baz: []string{"def", "ghi"}, }, - Bar: false, - Baz: []string{"def", "ghi"}, }, nil, }, @@ -181,20 +197,22 @@ var validUnpackTestCases = []struct { baz: ["def", "ghi"], } `, - struct { - Nested struct { - Foo string `allowNested:"true"` - } `blueprint:"filter(allowNested:\"true\")"` - Bar bool - Baz []string - }{ - Nested: struct { - Foo string `allowNested:"true"` + []interface{}{ + struct { + Nested struct { + Foo string `allowNested:"true"` + } `blueprint:"filter(allowNested:\"true\")"` + Bar bool + Baz []string }{ - Foo: "abc", + Nested: struct { + Foo string `allowNested:"true"` + }{ + Foo: "abc", + }, + Bar: false, + Baz: []string{"def", "ghi"}, }, - Bar: false, - Baz: []string{"def", "ghi"}, }, nil, }, @@ -208,18 +226,20 @@ var validUnpackTestCases = []struct { baz: ["def", "ghi"], } `, - struct { - Nested struct { - Foo string - } `blueprint:"filter(allowNested:\"true\")"` - Bar bool - Baz []string - }{ - Nested: struct{ Foo string }{ - Foo: "", + []interface{}{ + struct { + Nested struct { + Foo string + } `blueprint:"filter(allowNested:\"true\")"` + Bar bool + Baz []string + }{ + Nested: struct{ Foo string }{ + Foo: "", + }, + Bar: false, + Baz: []string{"def", "ghi"}, }, - Bar: false, - Baz: []string{"def", "ghi"}, }, []error{ &Error{ @@ -238,20 +258,22 @@ var validUnpackTestCases = []struct { }, } `, - struct { - EmbeddedStruct - Nested struct { - EmbeddedStruct - } - }{ - EmbeddedStruct: EmbeddedStruct{ - Name: "abc", - }, - Nested: struct { + []interface{}{ + struct { EmbeddedStruct + Nested struct { + EmbeddedStruct + } }{ EmbeddedStruct: EmbeddedStruct{ - Name: "def", + Name: "abc", + }, + Nested: struct { + EmbeddedStruct + }{ + EmbeddedStruct: EmbeddedStruct{ + Name: "def", + }, }, }, }, @@ -267,20 +289,22 @@ var validUnpackTestCases = []struct { }, } `, - struct { - EmbeddedInterface - Nested struct { - EmbeddedInterface - } - }{ - EmbeddedInterface: &struct{ Name string }{ - Name: "abc", - }, - Nested: struct { + []interface{}{ + struct { EmbeddedInterface + Nested struct { + EmbeddedInterface + } }{ EmbeddedInterface: &struct{ Name string }{ - Name: "def", + Name: "abc", + }, + Nested: struct { + EmbeddedInterface + }{ + EmbeddedInterface: &struct{ Name string }{ + Name: "def", + }, }, }, }, @@ -296,25 +320,27 @@ var validUnpackTestCases = []struct { }, } `, - struct { - Name string - EmbeddedStruct - Nested struct { - Name string - EmbeddedStruct - } - }{ - Name: "abc", - EmbeddedStruct: EmbeddedStruct{ - Name: "abc", - }, - Nested: struct { + []interface{}{ + struct { Name string EmbeddedStruct + Nested struct { + Name string + EmbeddedStruct + } }{ - Name: "def", + Name: "abc", EmbeddedStruct: EmbeddedStruct{ + Name: "abc", + }, + Nested: struct { + Name string + EmbeddedStruct + }{ Name: "def", + EmbeddedStruct: EmbeddedStruct{ + Name: "def", + }, }, }, }, @@ -330,27 +356,87 @@ var validUnpackTestCases = []struct { }, } `, - struct { - Name string - EmbeddedInterface - Nested struct { - Name string - EmbeddedInterface - } - }{ - Name: "abc", - EmbeddedInterface: &struct{ Name string }{ - Name: "abc", - }, - Nested: struct { + []interface{}{ + struct { Name string EmbeddedInterface + Nested struct { + Name string + EmbeddedInterface + } }{ - Name: "def", + Name: "abc", EmbeddedInterface: &struct{ Name string }{ + Name: "abc", + }, + Nested: struct { + Name string + EmbeddedInterface + }{ Name: "def", + EmbeddedInterface: &struct{ Name string }{ + Name: "def", + }, + }, + }, + }, + nil, + }, + + // Variables + {` + list = ["abc"] + string = "def" + list_with_variable = [string] + m { + name: string, + list: list, + list2: list_with_variable, + } + `, + []interface{}{ + struct { + Name string + List []string + List2 []string + }{ + Name: "def", + List: []string{"abc"}, + List2: []string{"def"}, + }, + }, + nil, + }, + + // Multiple property structs + {` + m { + nested: { + name: "abc", + } + } + `, + []interface{}{ + struct { + Nested struct { + Name string + } + }{ + Nested: struct{ Name string }{ + Name: "abc", + }, + }, + struct { + Nested struct { + Name string + } + }{ + Nested: struct{ Name string }{ + Name: "abc", }, }, + struct { + }{}, }, nil, }, @@ -362,7 +448,7 @@ type EmbeddedInterface interface{} func TestUnpackProperties(t *testing.T) { for _, testCase := range validUnpackTestCases { r := bytes.NewBufferString(testCase.input) - file, errs := parser.Parse("", r, nil) + file, errs := parser.ParseAndEval("", r, parser.NewScope(nil)) if len(errs) != 0 { t.Errorf("test case: %s", testCase.input) t.Errorf("unexpected parse errors:") @@ -372,30 +458,45 @@ func TestUnpackProperties(t *testing.T) { t.FailNow() } - module := file.Defs[0].(*parser.Module) - properties := proptools.CloneProperties(reflect.ValueOf(testCase.output)) - proptools.ZeroProperties(properties.Elem()) - _, errs = unpackProperties(module.Properties, properties.Interface()) - if len(errs) != 0 && len(testCase.errs) == 0 { - t.Errorf("test case: %s", testCase.input) - t.Errorf("unexpected unpack errors:") - for _, err := range errs { - t.Errorf(" %s", err) + for _, def := range file.Defs { + module, ok := def.(*parser.Module) + if !ok { + continue } - t.FailNow() - } else if !reflect.DeepEqual(errs, testCase.errs) { - t.Errorf("test case: %s", testCase.input) - t.Errorf("incorrect errors:") - t.Errorf(" expected: %+v", testCase.errs) - t.Errorf(" got: %+v", errs) - } - output := properties.Elem().Interface() - if !reflect.DeepEqual(output, testCase.output) { - t.Errorf("test case: %s", testCase.input) - t.Errorf("incorrect output:") - t.Errorf(" expected: %+v", testCase.output) - t.Errorf(" got: %+v", output) + output := []interface{}{} + for _, p := range testCase.output { + output = append(output, proptools.CloneEmptyProperties(reflect.ValueOf(p)).Interface()) + } + _, errs = unpackProperties(module.Properties, output...) + if len(errs) != 0 && len(testCase.errs) == 0 { + t.Errorf("test case: %s", testCase.input) + t.Errorf("unexpected unpack errors:") + for _, err := range errs { + t.Errorf(" %s", err) + } + t.FailNow() + } else if !reflect.DeepEqual(errs, testCase.errs) { + t.Errorf("test case: %s", testCase.input) + t.Errorf("incorrect errors:") + t.Errorf(" expected: %+v", testCase.errs) + t.Errorf(" got: %+v", errs) + } + + if len(output) != len(testCase.output) { + t.Fatalf("incorrect number of property structs, expected %d got %d", + len(testCase.output), len(output)) + } + + for i := range output { + got := reflect.ValueOf(output[i]).Elem().Interface() + if !reflect.DeepEqual(got, testCase.output[i]) { + t.Errorf("test case: %s", testCase.input) + t.Errorf("incorrect output:") + t.Errorf(" expected: %+v", testCase.output[i]) + t.Errorf(" got: %+v", got) + } + } } } } |