Refactor blueprint parser nodes to an interface

Refactor the blueprint parser Value object, which contained a Type enum
and members to hold every possible type, into an interface (now called
Expression).  Rename the existing Expression object that represented a binary
operator Operator.

Also adds and fixes some new printer test cases with mulitline expressions.

Change-Id: Icf4a20f92c8c2a27f18df8ca515a9d7f282ff133
This commit is contained in:
Colin Cross 2016-06-07 12:28:16 -07:00
parent aedd4903a9
commit e32cc80f20
13 changed files with 1251 additions and 922 deletions

View file

@ -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",

View file

@ -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

View file

@ -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 | $

View file

@ -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,

374
parser/ast.go Normal file
View file

@ -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)
}

View file

@ -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

View file

@ -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,210 +498,34 @@ 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))
return &Map{
LBracePos: lBracePos,
RBracePos: rBracePos,
Properties: properties,
}
}
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 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 {
vars map[string]*Assignment
inheritedVars map[string]*Assignment
@ -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
}

View file

@ -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",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
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",
},
},
&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",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
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",
},
},
&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",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
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",
},
},
&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])

View file

@ -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)

View file

@ -58,6 +58,49 @@ foo {
"uiop",
],
}
`,
},
{
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"],
}
`,
},
{

View file

@ -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) {

View file

@ -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
}

View file

@ -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,64 +149,14 @@ var validUnpackTestCases = []struct {
}
}
`,
struct {
Nested interface{}
}{
Nested: &struct{ Name string }{
Name: "def",
},
},
nil,
},
{`
m {
nested: {
foo: "abc",
},
bar: false,
baz: ["def", "ghi"],
}
`,
struct {
Nested struct {
Foo string
}
Bar bool
Baz []string
}{
Nested: struct{ Foo string }{
Foo: "abc",
},
Bar: false,
Baz: []string{"def", "ghi"},
},
nil,
},
{`
m {
nested: {
foo: "abc",
},
bar: false,
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 interface{}
}{
Foo: "abc",
Nested: &struct{ Name string }{
Name: "def",
},
},
Bar: false,
Baz: []string{"def", "ghi"},
},
nil,
},
@ -208,18 +170,76 @@ 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
}
Bar bool
Baz []string
}{
Nested: struct{ Foo string }{
Foo: "abc",
},
Bar: false,
Baz: []string{"def", "ghi"},
},
},
nil,
},
{`
m {
nested: {
foo: "abc",
},
bar: false,
baz: ["def", "ghi"],
}
`,
[]interface{}{
struct {
Nested struct {
Foo string `allowNested:"true"`
} `blueprint:"filter(allowNested:\"true\")"`
Bar bool
Baz []string
}{
Nested: struct {
Foo string `allowNested:"true"`
}{
Foo: "abc",
},
Bar: false,
Baz: []string{"def", "ghi"},
},
},
nil,
},
{`
m {
nested: {
foo: "abc",
},
bar: false,
baz: ["def", "ghi"],
}
`,
[]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,30 +356,90 @@ 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,
},
}
type EmbeddedStruct struct{ Name string }
@ -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)
}
}
}
}
}