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", name = "blueprint-parser",
pkgPath = "github.com/google/blueprint/parser", pkgPath = "github.com/google/blueprint/parser",
srcs = [ srcs = [
"parser/ast.go",
"parser/modify.go", "parser/modify.go",
"parser/parser.go", "parser/parser.go",
"parser/printer.go", "parser/printer.go",

View file

@ -9,7 +9,6 @@ import (
"bytes" "bytes"
"flag" "flag"
"fmt" "fmt"
"github.com/google/blueprint/parser"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -17,6 +16,8 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"unicode" "unicode"
"github.com/google/blueprint/parser"
) )
var ( var (
@ -123,8 +124,8 @@ func findModules(file *parser.File) (modified bool, errs []error) {
for _, def := range file.Defs { for _, def := range file.Defs {
if module, ok := def.(*parser.Module); ok { if module, ok := def.(*parser.Module); ok {
for _, prop := range module.Properties { for _, prop := range module.Properties {
if prop.Name.Name == "name" && prop.Value.Type == parser.String { if prop.Name.Name == "name" && prop.Value.Type() == parser.StringType {
if targetedModule(prop.Value.StringValue) { if targetedModule(prop.Value.Eval().(*parser.String).Value) {
m, newErrs := processModule(module, prop.Name.Name, file) m, newErrs := processModule(module, prop.Name.Name, file)
errs = append(errs, newErrs...) errs = append(errs, newErrs...)
modified = modified || m modified = modified || m
@ -142,7 +143,7 @@ func processModule(module *parser.Module, moduleName string,
for _, prop := range module.Properties { for _, prop := range module.Properties {
if prop.Name.Name == *parameter { if prop.Name.Name == *parameter {
modified, errs = processParameter(&prop.Value, *parameter, moduleName, file) modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
return return
} }
} }
@ -150,37 +151,38 @@ func processModule(module *parser.Module, moduleName string,
return false, nil 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) { file *parser.File) (modified bool, errs []error) {
if value.Type != parser.List { if _, ok := value.(*parser.Variable); ok {
return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
paramName, moduleName, value.Type.String())}
}
if value.Variable != "" {
return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported", return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
paramName, moduleName)} 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", return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
paramName, moduleName)} 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 { for _, a := range addIdents.idents {
m := parser.AddStringToList(value, a) m := parser.AddStringToList(list, a)
modified = modified || m modified = modified || m
} }
for _, r := range removeIdents.idents { for _, r := range removeIdents.idents {
m := parser.RemoveStringFromList(value, r) m := parser.RemoveStringFromList(list, r)
modified = modified || m modified = modified || m
} }
if (wasSorted || *sortLists) && modified { if (wasSorted || *sortLists) && modified {
parser.SortList(file, *value) parser.SortList(file, list)
} }
return modified, nil return modified, nil

View file

@ -81,7 +81,7 @@ default $
# Variant: # Variant:
# Type: bootstrap_go_package # Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:80:1 # Defined: Blueprints:81:1
build $ build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
@ -108,7 +108,7 @@ default $
# Variant: # Variant:
# Type: bootstrap_go_package # Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:99:1 # Defined: Blueprints:100:1
build $ build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
@ -128,7 +128,7 @@ default $
# Variant: # Variant:
# Type: bootstrap_go_package # Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:46:1 # Defined: Blueprints:47:1
build $ build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
@ -147,7 +147,8 @@ default $
build $ build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $ ${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/parser.go $
${g.bootstrap.srcDir}/parser/printer.go $ ${g.bootstrap.srcDir}/parser/printer.go $
${g.bootstrap.srcDir}/parser/sort.go | ${g.bootstrap.compileCmd} ${g.bootstrap.srcDir}/parser/sort.go | ${g.bootstrap.compileCmd}
@ -160,7 +161,7 @@ default $
# Variant: # Variant:
# Type: bootstrap_go_package # Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:52:1 # Defined: Blueprints:53:1
build $ build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
@ -175,7 +176,7 @@ default $
# Variant: # Variant:
# Type: bootstrap_go_package # Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1 # Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
# Defined: Blueprints:64:1 # Defined: Blueprints:65:1
build $ build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
@ -193,7 +194,7 @@ default $
# Variant: # Variant:
# Type: bootstrap_core_go_binary # Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 # 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: $ build ${g.bootstrap.buildDir}/.bootstrap/choosestage/obj/choosestage.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/choosestage/choosestage.go | $ g.bootstrap.compile ${g.bootstrap.srcDir}/choosestage/choosestage.go | $
@ -216,7 +217,7 @@ default ${g.bootstrap.BinDir}/choosestage
# Variant: # Variant:
# Type: bootstrap_core_go_binary # Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 # 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: $ build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $ g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $
@ -239,7 +240,7 @@ default ${g.bootstrap.BinDir}/gotestmain
# Variant: # Variant:
# Type: bootstrap_core_go_binary # Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 # 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: $ build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $ g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $
@ -262,7 +263,7 @@ default ${g.bootstrap.BinDir}/gotestrunner
# Variant: # Variant:
# Type: bootstrap_core_go_binary # Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1 # 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: $ build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | $ 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 { if assignment, local := scope.Get(v); assignment == nil || !local {
return nil, scanner.Position{}, nil return nil, scanner.Position{}, nil
} else { } else {
switch assignment.Value.Type { switch value := assignment.Value.Eval().(type) {
case parser.List: case *parser.List:
ret := make([]string, 0, len(assignment.Value.ListValue)) ret := make([]string, 0, len(value.Values))
for _, value := range assignment.Value.ListValue { for _, listValue := range value.Values {
if value.Type != parser.String { s, ok := listValue.(*parser.String)
if !ok {
// The parser should not produce this. // The parser should not produce this.
panic("non-string value found in list") panic("non-string value found in list")
} }
ret = append(ret, value.StringValue) ret = append(ret, s.Value)
} }
return ret, assignment.Pos, nil return ret, assignment.Pos, nil
case parser.Bool, parser.String: case *parser.Bool, *parser.String:
return nil, scanner.Position{}, &Error{ return nil, scanner.Position{}, &Error{
Err: fmt.Errorf("%q must be a list of strings", v), Err: fmt.Errorf("%q must be a list of strings", v),
Pos: assignment.Pos, Pos: assignment.Pos,
@ -890,10 +891,10 @@ func getStringFromScope(scope *parser.Scope, v string) (string, scanner.Position
if assignment, _ := scope.Get(v); assignment == nil { if assignment, _ := scope.Get(v); assignment == nil {
return "", scanner.Position{}, nil return "", scanner.Position{}, nil
} else { } else {
switch assignment.Value.Type { switch value := assignment.Value.Eval().(type) {
case parser.String: case *parser.String:
return assignment.Value.StringValue, assignment.Pos, nil return value.Value, assignment.Pos, nil
case parser.Bool, parser.List: case *parser.Bool, *parser.List:
return "", scanner.Position{}, &Error{ return "", scanner.Position{}, &Error{
Err: fmt.Errorf("%q must be a string", v), Err: fmt.Errorf("%q must be a string", v),
Pos: assignment.Pos, 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 package parser
func AddStringToList(value *Value, s string) (modified bool) { import "fmt"
if value.Type != List {
panic("expected list value, 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()))
} }
for _, v := range value.ListValue { if sv, ok := v.(*String); ok && sv.Value == s {
if v.Type != String {
panic("expected string in list, got " + value.Type.String())
}
if v.StringValue == s {
// string already exists // string already exists
return false return false
} }
} }
value.ListValue = append(value.ListValue, Value{ list.Values = append(list.Values, &String{
Type: String, LiteralPos: list.RBracePos,
Pos: value.EndPos, Value: s,
StringValue: s,
}) })
return true return true
} }
func RemoveStringFromList(value *Value, s string) (modified bool) { func RemoveStringFromList(list *List, s string) (modified bool) {
if value.Type != List { for i, v := range list.Values {
panic("expected list value, got " + value.Type.String()) if v.Type() != StringType {
panic(fmt.Errorf("expected string in list, got %s", v.Type()))
} }
for i, v := range value.ListValue { if sv, ok := v.(*String); ok && sv.Value == s {
if v.Type != String { list.Values = append(list.Values[:i], list.Values[i+1:]...)
panic("expected string in list, got " + value.Type.String())
}
if v.StringValue == s {
value.ListValue = append(value.ListValue[:i], value.ListValue[i+1:]...)
return true return true
} }
} }
return false return false

View file

@ -41,6 +41,7 @@ type File struct {
Name string Name string
Defs []Definition Defs []Definition
Comments []Comment Comments []Comment
Lines []scanner.Position
} }
func parse(p *parser) (file *File, errs []error) { func parse(p *parser) (file *File, errs []error) {
@ -178,8 +179,8 @@ func (p *parser) parseDefinitions() (defs []Definition) {
} }
} }
func (p *parser) parseAssignment(name string, func (p *parser) parseAssignment(name string, namePos scanner.Position,
namePos scanner.Position, assigner string) (assignment *Assignment) { assigner string) (assignment *Assignment) {
assignment = new(Assignment) assignment = new(Assignment)
@ -223,10 +224,8 @@ func (p *parser) parseAssignment(name string,
return return
} }
func (p *parser) parseModule(typ string, func (p *parser) parseModule(typ string, typPos scanner.Position) *Module {
typPos scanner.Position) (module *Module) {
module = new(Module)
compat := false compat := false
lbracePos := p.scanner.Position lbracePos := p.scanner.Position
if p.tok == '{' { if p.tok == '{' {
@ -234,7 +233,7 @@ func (p *parser) parseModule(typ string,
} }
if !p.accept(p.tok) { if !p.accept(p.tok) {
return return nil
} }
properties := p.parsePropertyList(true, compat) properties := p.parsePropertyList(true, compat)
rbracePos := p.scanner.Position rbracePos := p.scanner.Position
@ -244,11 +243,14 @@ func (p *parser) parseModule(typ string,
p.accept('}') p.accept('}')
} }
module.Type = Ident{typ, typPos} return &Module{
module.Properties = properties Type: Ident{typ, typPos},
module.LbracePos = lbracePos Map: Map{
module.RbracePos = rbracePos Properties: properties,
return LBracePos: lbracePos,
RBracePos: rbracePos,
},
}
} }
func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) { func (p *parser) parsePropertyList(isModule, compat bool) (properties []*Property) {
@ -298,7 +300,7 @@ func (p *parser) parseProperty(isModule, compat bool) (property *Property) {
return return
} }
func (p *parser) parseExpression() (value Value) { func (p *parser) parseExpression() (value Expression) {
value = p.parseValue() value = p.parseValue()
switch p.tok { switch p.tok {
case '+': case '+':
@ -308,50 +310,48 @@ func (p *parser) parseExpression() (value Value) {
} }
} }
func (p *parser) evaluateOperator(value1, value2 Value, operator rune, func (p *parser) evaluateOperator(value1, value2 Expression, operator rune,
pos scanner.Position) (Value, error) { pos scanner.Position) (*Operator, error) {
value := Value{} value := value1
if p.eval { if p.eval {
if value1.Type != value2.Type { e1 := value1.Eval()
return Value{}, fmt.Errorf("mismatched type in operator %c: %s != %s", operator, e2 := value2.Eval()
value1.Type, value2.Type) if e1.Type() != e2.Type() {
return nil, fmt.Errorf("mismatched type in operator %c: %s != %s", operator,
e1.Type(), e2.Type())
} }
value = value1 value = e1.Copy()
value.Variable = ""
switch operator { switch operator {
case '+': case '+':
switch value1.Type { switch v := value.(type) {
case String: case *String:
value.StringValue = value1.StringValue + value2.StringValue v.Value += e2.(*String).Value
case List: case *List:
value.ListValue = append([]Value{}, value1.ListValue...) v.Values = append(v.Values, e2.(*List).Values...)
value.ListValue = append(value.ListValue, value2.ListValue...) case *Map:
case Map:
var err error 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 { if err != nil {
return Value{}, err return nil, err
} }
default: default:
return Value{}, fmt.Errorf("operator %c not supported on type %s", operator, return nil, fmt.Errorf("operator %c not supported on type %s", operator, v.Type())
value1.Type)
} }
default: default:
panic("unknown operator " + string(operator)) panic("unknown operator " + string(operator))
} }
} }
value.Expression = &Expression{ return &Operator{
Args: [2]Value{value1, value2}, Args: [2]Expression{value1, value2},
Operator: operator, Operator: operator,
Pos: pos, OperatorPos: pos,
} Value: value,
}, nil
return value, nil
} }
func (p *parser) addMaps(map1, map2 []*Property, pos scanner.Position) ([]*Property, error) { 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 return ret, nil
} }
func (p *parser) parseOperator(value1 Value) Value { func (p *parser) parseOperator(value1 Expression) *Operator {
operator := p.tok operator := p.tok
pos := p.scanner.Position pos := p.scanner.Position
p.accept(operator) p.accept(operator)
@ -405,13 +405,14 @@ func (p *parser) parseOperator(value1 Value) Value {
value, err := p.evaluateOperator(value1, value2, operator, pos) value, err := p.evaluateOperator(value1, value2, operator, pos)
if err != nil { if err != nil {
p.error(err) p.error(err)
return Value{} return nil
} }
return value return value
} }
func (p *parser) parseValue() (value Value) { func (p *parser) parseValue() (value Expression) {
switch p.tok { switch p.tok {
case scanner.Ident: case scanner.Ident:
return p.parseVariable() 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 { switch text := p.scanner.TokenText(); text {
case "true": case "true", "false":
value.Type = Bool value = &Bool{
value.BoolValue = true LiteralPos: p.scanner.Position,
case "false": Value: text == "true",
value.Type = Bool }
value.BoolValue = false
default: default:
variable := p.scanner.TokenText()
if p.eval { if p.eval {
if assignment, local := p.scope.Get(variable); assignment == nil { if assignment, local := p.scope.Get(text); assignment == nil {
p.errorf("variable %q is not set", variable) p.errorf("variable %q is not set", text)
} else { } else {
if local { if local {
assignment.Referenced = true assignment.Referenced = true
@ -448,40 +449,44 @@ func (p *parser) parseVariable() (value Value) {
value = assignment.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) p.accept(scanner.Ident)
return return value
} }
func (p *parser) parseStringValue() (value Value) { func (p *parser) parseStringValue() *String {
value.Type = String
value.Pos = p.scanner.Position
str, err := strconv.Unquote(p.scanner.TokenText()) str, err := strconv.Unquote(p.scanner.TokenText())
if err != nil { if err != nil {
p.errorf("couldn't parse string: %s", err) 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) p.accept(scanner.String)
return return value
} }
func (p *parser) parseListValue() (value Value) { func (p *parser) parseListValue() *List {
value.Type = List lBracePos := p.scanner.Position
value.Pos = p.scanner.Position
if !p.accept('[') { if !p.accept('[') {
return return nil
} }
var elements []Value var elements []Expression
for p.tok != ']' { for p.tok != ']' {
element := p.parseExpression() element := p.parseExpression()
if p.eval && element.Type != String { if p.eval && element.Type() != StringType {
p.errorf("Expected string in list, found %s", element.String()) p.errorf("Expected string in list, found %s", element.Type().String())
return return nil
} }
elements = append(elements, element) elements = append(elements, element)
@ -493,208 +498,32 @@ func (p *parser) parseListValue() (value Value) {
p.accept(',') p.accept(',')
} }
value.ListValue = elements rBracePos := p.scanner.Position
value.EndPos = p.scanner.Position
p.accept(']') p.accept(']')
return
return &List{
LBracePos: lBracePos,
RBracePos: rBracePos,
Values: elements,
}
} }
func (p *parser) parseMapValue() (value Value) { func (p *parser) parseMapValue() *Map {
value.Type = Map lBracePos := p.scanner.Position
value.Pos = p.scanner.Position
if !p.accept('{') { if !p.accept('{') {
return return nil
} }
properties := p.parsePropertyList(false, false) properties := p.parsePropertyList(false, false)
value.MapValue = properties
value.EndPos = p.scanner.Position rBracePos := p.scanner.Position
p.accept('}') p.accept('}')
return
}
type Expression struct { return &Map{
Args [2]Value LBracePos: lBracePos,
Operator rune RBracePos: rBracePos,
Pos scanner.Position Properties: properties,
} }
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 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 { type Scope struct {
@ -774,58 +603,3 @@ func (s *Scope) String() string {
return strings.Join(ret, "\n") 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

@ -40,8 +40,10 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(3, 2, 3)}, Type: Ident{"foo", mkpos(3, 2, 3)},
LbracePos: mkpos(7, 2, 7), Map: Map{
RbracePos: mkpos(8, 2, 8), LBracePos: mkpos(7, 2, 7),
RBracePos: mkpos(8, 2, 8),
},
}, },
}, },
nil, nil,
@ -55,16 +57,17 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(3, 2, 3)}, Type: Ident{"foo", mkpos(3, 2, 3)},
LbracePos: mkpos(7, 2, 7), Map: Map{
RbracePos: mkpos(27, 4, 3), LBracePos: mkpos(7, 2, 7),
RBracePos: mkpos(27, 4, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"name", mkpos(12, 3, 4)}, Name: Ident{"name", mkpos(12, 3, 4)},
Pos: mkpos(16, 3, 8), Pos: mkpos(16, 3, 8),
Value: Value{ Value: &String{
Type: String, LiteralPos: mkpos(18, 3, 10),
Pos: mkpos(18, 3, 10), Value: "abc",
StringValue: "abc", },
}, },
}, },
}, },
@ -81,16 +84,17 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(3, 2, 3)}, Type: Ident{"foo", mkpos(3, 2, 3)},
LbracePos: mkpos(7, 2, 7), Map: Map{
RbracePos: mkpos(28, 4, 3), LBracePos: mkpos(7, 2, 7),
RBracePos: mkpos(28, 4, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"isGood", mkpos(12, 3, 4)}, Name: Ident{"isGood", mkpos(12, 3, 4)},
Pos: mkpos(18, 3, 10), Pos: mkpos(18, 3, 10),
Value: Value{ Value: &Bool{
Type: Bool, LiteralPos: mkpos(20, 3, 12),
Pos: mkpos(20, 3, 12), Value: true,
BoolValue: true, },
}, },
}, },
}, },
@ -108,41 +112,37 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(3, 2, 3)}, Type: Ident{"foo", mkpos(3, 2, 3)},
LbracePos: mkpos(7, 2, 7), Map: Map{
RbracePos: mkpos(67, 5, 3), LBracePos: mkpos(7, 2, 7),
RBracePos: mkpos(67, 5, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"stuff", mkpos(12, 3, 4)}, Name: Ident{"stuff", mkpos(12, 3, 4)},
Pos: mkpos(17, 3, 9), Pos: mkpos(17, 3, 9),
Value: Value{ Value: &List{
Type: List, LBracePos: mkpos(19, 3, 11),
Pos: mkpos(19, 3, 11), RBracePos: mkpos(63, 4, 19),
EndPos: mkpos(63, 4, 19), Values: []Expression{
ListValue: []Value{ &String{
Value{ LiteralPos: mkpos(20, 3, 12),
Type: String, Value: "asdf",
Pos: mkpos(20, 3, 12),
StringValue: "asdf",
}, },
Value{ &String{
Type: String, LiteralPos: mkpos(28, 3, 20),
Pos: mkpos(28, 3, 20), Value: "jkl;",
StringValue: "jkl;",
}, },
Value{ &String{
Type: String, LiteralPos: mkpos(36, 3, 28),
Pos: mkpos(36, 3, 28), Value: "qwert",
StringValue: "qwert",
}, },
Value{ &String{
Type: String, LiteralPos: mkpos(49, 4, 5),
Pos: mkpos(49, 4, 5), Value: "uiop",
StringValue: "uiop", },
&String{
LiteralPos: mkpos(57, 4, 13),
Value: "bnm,",
}, },
Value{
Type: String,
Pos: mkpos(57, 4, 13),
StringValue: "bnm,",
}, },
}, },
}, },
@ -164,33 +164,32 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(3, 2, 3)}, Type: Ident{"foo", mkpos(3, 2, 3)},
LbracePos: mkpos(7, 2, 7), Map: Map{
RbracePos: mkpos(62, 7, 3), LBracePos: mkpos(7, 2, 7),
RBracePos: mkpos(62, 7, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"stuff", mkpos(12, 3, 4)}, Name: Ident{"stuff", mkpos(12, 3, 4)},
Pos: mkpos(17, 3, 9), Pos: mkpos(17, 3, 9),
Value: Value{ Value: &Map{
Type: Map, LBracePos: mkpos(19, 3, 11),
Pos: mkpos(19, 3, 11), RBracePos: mkpos(58, 6, 4),
EndPos: mkpos(58, 6, 4), Properties: []*Property{
MapValue: []*Property{
{ {
Name: Ident{"isGood", mkpos(25, 4, 5)}, Name: Ident{"isGood", mkpos(25, 4, 5)},
Pos: mkpos(31, 4, 11), Pos: mkpos(31, 4, 11),
Value: Value{ Value: &Bool{
Type: Bool, LiteralPos: mkpos(33, 4, 13),
Pos: mkpos(33, 4, 13), Value: true,
BoolValue: true,
}, },
}, },
{ {
Name: Ident{"name", mkpos(43, 5, 5)}, Name: Ident{"name", mkpos(43, 5, 5)},
Pos: mkpos(47, 5, 9), Pos: mkpos(47, 5, 9),
Value: Value{ Value: &String{
Type: String, LiteralPos: mkpos(49, 5, 11),
Pos: mkpos(49, 5, 11), Value: "bar",
StringValue: "bar", },
}, },
}, },
}, },
@ -204,7 +203,7 @@ var validParseTestCases = []struct {
{` {`
// comment1 // comment1
foo { foo /* test */ {
// comment2 // comment2
isGood: true, // comment3 isGood: true, // comment3
} }
@ -212,16 +211,17 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(17, 3, 3)}, Type: Ident{"foo", mkpos(17, 3, 3)},
LbracePos: mkpos(21, 3, 7), Map: Map{
RbracePos: mkpos(70, 6, 3), LBracePos: mkpos(32, 3, 18),
RBracePos: mkpos(81, 6, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"isGood", mkpos(41, 5, 4)}, Name: Ident{"isGood", mkpos(52, 5, 4)},
Pos: mkpos(47, 5, 10), Pos: mkpos(58, 5, 10),
Value: Value{ Value: &Bool{
Type: Bool, LiteralPos: mkpos(60, 5, 12),
Pos: mkpos(49, 5, 12), Value: true,
BoolValue: true, },
}, },
}, },
}, },
@ -230,15 +230,19 @@ var validParseTestCases = []struct {
[]Comment{ []Comment{
Comment{ Comment{
Comment: []string{"// comment1"}, Comment: []string{"// comment1"},
Pos: mkpos(3, 2, 3), Slash: mkpos(3, 2, 3),
},
Comment{
Comment: []string{"/* test */"},
Slash: mkpos(21, 3, 7),
}, },
Comment{ Comment{
Comment: []string{"// comment2"}, Comment: []string{"// comment2"},
Pos: mkpos(26, 4, 4), Slash: mkpos(37, 4, 4),
}, },
Comment{ Comment{
Comment: []string{"// comment3"}, Comment: []string{"// comment3"},
Pos: mkpos(56, 5, 19), Slash: mkpos(67, 5, 19),
}, },
}, },
}, },
@ -255,32 +259,34 @@ var validParseTestCases = []struct {
[]Definition{ []Definition{
&Module{ &Module{
Type: Ident{"foo", mkpos(3, 2, 3)}, Type: Ident{"foo", mkpos(3, 2, 3)},
LbracePos: mkpos(7, 2, 7), Map: Map{
RbracePos: mkpos(27, 4, 3), LBracePos: mkpos(7, 2, 7),
RBracePos: mkpos(27, 4, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"name", mkpos(12, 3, 4)}, Name: Ident{"name", mkpos(12, 3, 4)},
Pos: mkpos(16, 3, 8), Pos: mkpos(16, 3, 8),
Value: Value{ Value: &String{
Type: String, LiteralPos: mkpos(18, 3, 10),
Pos: mkpos(18, 3, 10), Value: "abc",
StringValue: "abc", },
}, },
}, },
}, },
}, },
&Module{ &Module{
Type: Ident{"bar", mkpos(32, 6, 3)}, Type: Ident{"bar", mkpos(32, 6, 3)},
LbracePos: mkpos(36, 6, 7), Map: Map{
RbracePos: mkpos(56, 8, 3), LBracePos: mkpos(36, 6, 7),
RBracePos: mkpos(56, 8, 3),
Properties: []*Property{ Properties: []*Property{
{ {
Name: Ident{"name", mkpos(41, 7, 4)}, Name: Ident{"name", mkpos(41, 7, 4)},
Pos: mkpos(45, 7, 8), Pos: mkpos(45, 7, 8),
Value: Value{ Value: &String{
Type: String, LiteralPos: mkpos(47, 7, 10),
Pos: mkpos(47, 7, 10), Value: "def",
StringValue: "def", },
}, },
}, },
}, },
@ -299,15 +305,13 @@ var validParseTestCases = []struct {
&Assignment{ &Assignment{
Name: Ident{"foo", mkpos(3, 2, 3)}, Name: Ident{"foo", mkpos(3, 2, 3)},
Pos: mkpos(7, 2, 7), Pos: mkpos(7, 2, 7),
Value: Value{ Value: &String{
Type: String, LiteralPos: mkpos(9, 2, 9),
Pos: mkpos(9, 2, 9), Value: "stuff",
StringValue: "stuff",
}, },
OrigValue: Value{ OrigValue: &String{
Type: String, LiteralPos: mkpos(9, 2, 9),
Pos: mkpos(9, 2, 9), Value: "stuff",
StringValue: "stuff",
}, },
Assigner: "=", Assigner: "=",
Referenced: true, Referenced: true,
@ -315,17 +319,21 @@ var validParseTestCases = []struct {
&Assignment{ &Assignment{
Name: Ident{"bar", mkpos(19, 3, 3)}, Name: Ident{"bar", mkpos(19, 3, 3)},
Pos: mkpos(23, 3, 7), Pos: mkpos(23, 3, 7),
Value: Value{ Value: &Variable{
Type: String, Name: "foo",
Pos: mkpos(25, 3, 9), NamePos: mkpos(25, 3, 9),
StringValue: "stuff", Value: &String{
Variable: "foo", LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
OrigValue: &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",
}, },
Assigner: "=", Assigner: "=",
Referenced: true, Referenced: true,
@ -333,50 +341,64 @@ var validParseTestCases = []struct {
&Assignment{ &Assignment{
Name: Ident{"baz", mkpos(31, 4, 3)}, Name: Ident{"baz", mkpos(31, 4, 3)},
Pos: mkpos(35, 4, 7), Pos: mkpos(35, 4, 7),
Value: Value{ Value: &Operator{
Type: String, OperatorPos: mkpos(41, 4, 13),
Pos: mkpos(37, 4, 9),
StringValue: "stuffstuff",
Expression: &Expression{
Args: [2]Value{
{
Type: String,
Pos: mkpos(37, 4, 9),
StringValue: "stuff",
Variable: "foo",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
},
},
Operator: '+', Operator: '+',
Pos: mkpos(41, 4, 13), 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",
}, },
}, },
OrigValue: Value{ &Variable{
Type: String, Name: "bar",
Pos: mkpos(37, 4, 9), NamePos: mkpos(43, 4, 15),
StringValue: "stuffstuff", Value: &Variable{
Expression: &Expression{ Name: "foo",
Args: [2]Value{ NamePos: mkpos(25, 3, 9),
{ Value: &String{
Type: String, LiteralPos: mkpos(9, 2, 9),
Pos: mkpos(37, 4, 9), Value: "stuff",
StringValue: "stuff",
Variable: "foo",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
}, },
}, },
},
},
},
OrigValue: &Operator{
OperatorPos: mkpos(41, 4, 13),
Operator: '+', Operator: '+',
Pos: mkpos(41, 4, 13), 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",
},
},
},
}, },
}, },
Assigner: "=", Assigner: "=",
@ -385,69 +407,90 @@ var validParseTestCases = []struct {
&Assignment{ &Assignment{
Name: Ident{"boo", mkpos(49, 5, 3)}, Name: Ident{"boo", mkpos(49, 5, 3)},
Pos: mkpos(53, 5, 7), Pos: mkpos(53, 5, 7),
Value: Value{ Value: &Operator{
Type: String, Args: [2]Expression{
Pos: mkpos(55, 5, 9), &Variable{
StringValue: "stuffstuffstuff", Name: "baz",
Expression: &Expression{ NamePos: mkpos(55, 5, 9),
Args: [2]Value{ Value: &Operator{
{ OperatorPos: mkpos(41, 4, 13),
Type: String,
Pos: mkpos(55, 5, 9),
StringValue: "stuffstuff",
Variable: "baz",
Expression: &Expression{
Args: [2]Value{
{
Type: String,
Pos: mkpos(37, 4, 9),
StringValue: "stuff",
Variable: "foo",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
},
},
Operator: '+', Operator: '+',
Pos: mkpos(41, 4, 13), 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{
Variable: "foo", Name: "bar",
Type: String, NamePos: mkpos(43, 4, 15),
Pos: mkpos(68, 6, 10), Value: &Variable{
StringValue: "stuff", Name: "foo",
NamePos: mkpos(25, 3, 9),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
}, },
}, },
Pos: mkpos(66, 6, 8), },
},
},
},
&Variable{
Name: "foo",
NamePos: mkpos(68, 6, 10),
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
},
OperatorPos: mkpos(66, 6, 8),
Operator: '+', Operator: '+',
Value: &String{
LiteralPos: mkpos(9, 2, 9),
Value: "stuffstuffstuff",
}, },
}, },
OrigValue: Value{ OrigValue: &Variable{
Type: String, Name: "baz",
Pos: mkpos(55, 5, 9), NamePos: mkpos(55, 5, 9),
StringValue: "stuffstuff", Value: &Operator{
Variable: "baz", OperatorPos: mkpos(41, 4, 13),
Expression: &Expression{
Args: [2]Value{
{
Type: String,
Pos: mkpos(37, 4, 9),
StringValue: "stuff",
Variable: "foo",
},
{
Type: String,
Pos: mkpos(43, 4, 15),
StringValue: "stuff",
Variable: "bar",
},
},
Operator: '+', Operator: '+',
Pos: mkpos(41, 4, 13), 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",
},
},
},
},
}, },
}, },
Assigner: "=", Assigner: "=",
@ -455,17 +498,21 @@ var validParseTestCases = []struct {
&Assignment{ &Assignment{
Name: Ident{"boo", mkpos(61, 6, 3)}, Name: Ident{"boo", mkpos(61, 6, 3)},
Pos: mkpos(66, 6, 8), Pos: mkpos(66, 6, 8),
Value: Value{ Value: &Variable{
Type: String, Name: "foo",
Pos: mkpos(68, 6, 10), NamePos: mkpos(68, 6, 10),
StringValue: "stuff", Value: &String{
Variable: "foo", LiteralPos: mkpos(9, 2, 9),
Value: "stuff",
},
},
OrigValue: &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",
}, },
Assigner: "+=", Assigner: "+=",
}, },
@ -504,7 +551,7 @@ func TestParseValidInput(t *testing.T) {
if len(file.Comments) == len(testCase.comments) { if len(file.Comments) == len(testCase.comments) {
for i := range file.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("test case: %s", testCase.input)
t.Errorf("incorrect comment %d:", i) t.Errorf("incorrect comment %d:", i)
t.Errorf(" expected: %s", testCase.comments[i]) t.Errorf(" expected: %s", testCase.comments[i])

View file

@ -22,7 +22,7 @@ import (
"unicode" "unicode"
) )
var noPos = scanner.Position{} var noPos scanner.Position
type printer struct { type printer struct {
defs []Definition defs []Definition
@ -91,96 +91,94 @@ func (p *printer) printAssignment(assignment *Assignment) {
p.requestSpace() p.requestSpace()
p.printToken(assignment.Assigner, assignment.Pos) p.printToken(assignment.Assigner, assignment.Pos)
p.requestSpace() p.requestSpace()
p.printValue(assignment.OrigValue) p.printExpression(assignment.OrigValue)
p.requestNewline() p.requestNewline()
} }
func (p *printer) printModule(module *Module) { func (p *printer) printModule(module *Module) {
p.printToken(module.Type.Name, module.Type.Pos) p.printToken(module.Type.Name, module.Type.Pos)
p.printMap(module.Properties, module.LbracePos, module.RbracePos) p.printMap(&module.Map)
p.requestDoubleNewline() p.requestDoubleNewline()
} }
func (p *printer) printValue(value Value) { func (p *printer) printExpression(value Expression) {
if value.Variable != "" { switch v := value.(type) {
p.printToken(value.Variable, value.Pos) case *Variable:
} else if value.Expression != nil { p.printToken(v.Name, v.NamePos)
p.printExpression(*value.Expression) case *Operator:
} else { p.printOperator(v)
switch value.Type { case *Bool:
case Bool:
var s string var s string
if value.BoolValue { if v.Value {
s = "true" s = "true"
} else { } else {
s = "false" s = "false"
} }
p.printToken(s, value.Pos) p.printToken(s, v.LiteralPos)
case String: case *String:
p.printToken(strconv.Quote(value.StringValue), value.Pos) p.printToken(strconv.Quote(v.Value), v.LiteralPos)
case List: case *List:
p.printList(value.ListValue, value.Pos, value.EndPos) p.printList(v.Values, v.LBracePos, v.RBracePos)
case Map: case *Map:
p.printMap(value.MapValue, value.Pos, value.EndPos) p.printMap(v)
default: default:
panic(fmt.Errorf("bad property type: %d", value.Type)) 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.requestSpace()
p.printToken("[", pos) p.printToken("[", pos)
if len(list) > 1 || pos.Line != endPos.Line { if len(list) > 1 || pos.Line != endPos.Line {
p.requestNewline() p.requestNewline()
p.indent(p.curIndent() + 4) p.indent(p.curIndent() + 4)
for _, value := range list { for _, value := range list {
p.printValue(value) p.printExpression(value)
p.printToken(",", noPos) p.printToken(",", noPos)
p.requestNewline() p.requestNewline()
} }
p.unindent(endPos) p.unindent(endPos)
} else { } else {
for _, value := range list { for _, value := range list {
p.printValue(value) p.printExpression(value)
} }
} }
p.printToken("]", endPos) p.printToken("]", endPos)
} }
func (p *printer) printMap(list []*Property, pos, endPos scanner.Position) { func (p *printer) printMap(m *Map) {
p.requestSpace() p.requestSpace()
p.printToken("{", pos) p.printToken("{", m.LBracePos)
if len(list) > 0 || pos.Line != endPos.Line { if len(m.Properties) > 0 || m.LBracePos.Line != m.RBracePos.Line {
p.requestNewline() p.requestNewline()
p.indent(p.curIndent() + 4) p.indent(p.curIndent() + 4)
for _, prop := range list { for _, prop := range m.Properties {
p.printProperty(prop) p.printProperty(prop)
p.printToken(",", noPos) p.printToken(",", noPos)
p.requestNewline() p.requestNewline()
} }
p.unindent(endPos) p.unindent(m.RBracePos)
} }
p.printToken("}", endPos) p.printToken("}", m.RBracePos)
} }
func (p *printer) printExpression(expression Expression) { func (p *printer) printOperator(operator *Operator) {
p.printValue(expression.Args[0]) p.printExpression(operator.Args[0])
p.requestSpace() p.requestSpace()
p.printToken(string(expression.Operator), expression.Pos) p.printToken(string(operator.Operator), operator.OperatorPos)
if expression.Args[0].Pos.Line == expression.Args[1].Pos.Line { if operator.Args[0].End().Line == operator.Args[1].Pos().Line {
p.requestSpace() p.requestSpace()
} else { } else {
p.requestNewline() p.requestNewline()
} }
p.printValue(expression.Args[1]) p.printExpression(operator.Args[1])
} }
func (p *printer) printProperty(property *Property) { func (p *printer) printProperty(property *Property) {
p.printToken(property.Name.Name, property.Name.Pos) p.printToken(property.Name.Name, property.Name.Pos)
p.printToken(":", property.Pos) p.printToken(":", property.Pos)
p.requestSpace() p.requestSpace()
p.printValue(property.Value) p.printExpression(property.Value)
} }
// Print a single token, including any necessary comments or whitespace between // 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 // Print any in-line (single line /* */) comments that appear _before_ pos
func (p *printer) printInLineCommentsBefore(pos scanner.Position) { 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] c := p.comments[p.curComment]
if c.Comment[0][0:2] == "//" || len(c.Comment) > 1 { if c.Comment[0][0:2] == "//" || len(c.Comment) > 1 {
p.skippedComments = append(p.skippedComments, c) p.skippedComments = append(p.skippedComments, c)
@ -225,16 +223,16 @@ func (p *printer) printInLineCommentsBefore(pos scanner.Position) {
// by pos // by pos
func (p *printer) printEndOfLineCommentsBefore(pos scanner.Position) { func (p *printer) printEndOfLineCommentsBefore(pos scanner.Position) {
for _, c := range p.skippedComments { for _, c := range p.skippedComments {
if !p.requestNewlinesForPos(c.Pos) { if !p.requestNewlinesForPos(c.Slash) {
p.requestSpace() p.requestSpace()
} }
p.printComment(c) p.printComment(c)
p._requestNewline() p._requestNewline()
} }
p.skippedComments = []Comment{} 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] c := p.comments[p.curComment]
if !p.requestNewlinesForPos(c.Pos) { if !p.requestNewlinesForPos(c.Slash) {
p.requestSpace() p.requestSpace()
} }
p.printComment(c) p.printComment(c)
@ -303,7 +301,7 @@ func (p *printer) flushSpace() {
// Print a single comment, which may be a multi-line comment // Print a single comment, which may be a multi-line comment
func (p *printer) printComment(comment Comment) { func (p *printer) printComment(comment Comment) {
pos := comment.Pos pos := comment.Slash
for i, line := range comment.Comment { for i, line := range comment.Comment {
line = strings.TrimRightFunc(line, unicode.IsSpace) line = strings.TrimRightFunc(line, unicode.IsSpace)
p.flushSpace() 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 // Print any comments that occur after the last token, and a trailing newline
func (p *printer) flush() { func (p *printer) flush() {
for _, c := range p.skippedComments { for _, c := range p.skippedComments {
if !p.requestNewlinesForPos(c.Pos) { if !p.requestNewlinesForPos(c.Slash) {
p.requestSpace() p.requestSpace()
} }
p.printComment(c) p.printComment(c)
} }
for p.curComment < len(p.comments) { for p.curComment < len(p.comments) {
c := p.comments[p.curComment] c := p.comments[p.curComment]
if !p.requestNewlinesForPos(c.Pos) { if !p.requestNewlinesForPos(c.Slash) {
p.requestSpace() p.requestSpace()
} }
p.printComment(c) p.printComment(c)

View file

@ -58,6 +58,49 @@ foo {
"uiop", "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)) sort.Sort(commentsByOffset(file.Comments))
} }
func SortList(file *File, value Value) { func SortList(file *File, list *List) {
for i := 0; i < len(value.ListValue); i++ { for i := 0; i < len(list.Values); i++ {
// Find a set of values on contiguous lines // Find a set of values on contiguous lines
line := value.ListValue[i].Pos.Line line := list.Values[i].Pos().Line
var j int var j int
for j = i + 1; j < len(value.ListValue); j++ { for j = i + 1; j < len(list.Values); j++ {
if value.ListValue[j].Pos.Line > line+1 { if list.Values[j].Pos().Line > line+1 {
break break
} }
line = value.ListValue[j].Pos.Line line = list.Values[j].Pos().Line
} }
nextPos := value.EndPos nextPos := list.End()
if j < len(value.ListValue) { if j < len(list.Values) {
nextPos = value.ListValue[j].Pos nextPos = list.Values[j].Pos()
} }
sortSubList(value.ListValue[i:j], nextPos, file) sortSubList(list.Values[i:j], nextPos, file)
i = j - 1 i = j - 1
} }
} }
func ListIsSorted(value Value) bool { func ListIsSorted(list *List) bool {
for i := 0; i < len(value.ListValue); i++ { for i := 0; i < len(list.Values); i++ {
// Find a set of values on contiguous lines // Find a set of values on contiguous lines
line := value.ListValue[i].Pos.Line line := list.Values[i].Pos().Line
var j int var j int
for j = i + 1; j < len(value.ListValue); j++ { for j = i + 1; j < len(list.Values); j++ {
if value.ListValue[j].Pos.Line > line+1 { if list.Values[j].Pos().Line > line+1 {
break 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 return false
} }
i = j - 1 i = j - 1
@ -74,55 +74,49 @@ func ListIsSorted(value Value) bool {
return true return true
} }
func sortListsInValue(value Value, file *File) { func sortListsInValue(value Expression, file *File) {
if value.Variable != "" { switch v := value.(type) {
return case *Variable:
} // Nothing
case *Operator:
if value.Expression != nil { sortListsInValue(v.Args[0], file)
sortListsInValue(value.Expression.Args[0], file) sortListsInValue(v.Args[1], file)
sortListsInValue(value.Expression.Args[1], file) case *Map:
return for _, p := range v.Properties {
}
if value.Type == Map {
for _, p := range value.MapValue {
sortListsInValue(p.Value, file) sortListsInValue(p.Value, file)
} }
return case *List:
} else if value.Type != List { SortList(file, v)
return }
} }
SortList(file, value) func sortSubList(values []Expression, nextPos scanner.Position, file *File) {
}
func sortSubList(values []Value, nextPos scanner.Position, file *File) {
l := make(elemList, len(values)) l := make(elemList, len(values))
for i, v := range values { for i, v := range values {
if v.Type != String { s, ok := v.(*String)
if !ok {
panic("list contains non-string element") panic("list contains non-string element")
} }
n := nextPos n := nextPos
if i < len(values)-1 { 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) sort.Sort(l)
copyValues := append([]Value{}, values...) copyValues := append([]Expression{}, values...)
copyComments := append([]Comment{}, file.Comments...) copyComments := append([]Comment{}, file.Comments...)
curPos := values[0].Pos curPos := values[0].Pos()
for i, e := range l { for i, e := range l {
values[i] = copyValues[e.i] values[i] = copyValues[e.i]
values[i].Pos = curPos values[i].(*String).LiteralPos = curPos
for j, c := range copyComments { for j, c := range copyComments {
if c.Pos.Offset > e.pos.Offset && c.Pos.Offset < e.nextPos.Offset { if c.Pos().Offset > e.pos.Offset && c.Pos().Offset < e.nextPos.Offset {
file.Comments[j].Pos.Line = curPos.Line file.Comments[j].Slash.Line = curPos.Line
file.Comments[j].Pos.Offset += values[i].Pos.Offset - e.pos.Offset 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 := "" prev := ""
for _, v := range values { for _, v := range values {
if v.Type != String { s, ok := v.(*String)
if !ok {
panic("list contains non-string element") panic("list contains non-string element")
} }
if prev > v.StringValue { if prev > s.Value {
return false return false
} }
prev = v.StringValue prev = s.Value
} }
return true return true
@ -174,7 +169,7 @@ func (l commentsByOffset) Len() int {
} }
func (l commentsByOffset) Less(i, j int) bool { 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) { 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. // We've already added this property.
continue continue
} }
errs = append(errs, &Error{ errs = append(errs, &Error{
Err: fmt.Errorf("property %q already defined", name), Err: fmt.Errorf("property %q already defined", name),
Pos: propertyDef.Pos, Pos: propertyDef.Pos,
@ -276,47 +275,49 @@ func unpackStructValue(namePrefix string, structValue reflect.Value,
} }
func unpackBool(boolValue reflect.Value, property *parser.Property) []error { 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{ return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q", fmt.Errorf("%s: can't assign %s value to bool property %q",
property.Value.Pos, property.Value.Type, parser.Bool, property.Value.Pos, property.Value.Type, property.Name),
property.Name),
} }
} }
boolValue.SetBool(property.Value.BoolValue) boolValue.SetBool(b.Value)
return nil return nil
} }
func unpackString(stringValue reflect.Value, func unpackString(stringValue reflect.Value,
property *parser.Property) []error { property *parser.Property) []error {
if property.Value.Type != parser.String { s, ok := property.Value.Eval().(*parser.String)
if !ok {
return []error{ return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q", fmt.Errorf("%s: can't assign %s value to string property %q",
property.Value.Pos, property.Value.Type, parser.String, property.Value.Pos, property.Value.Type, property.Name),
property.Name),
} }
} }
stringValue.SetString(property.Value.StringValue) stringValue.SetString(s.Value)
return nil return nil
} }
func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error { 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{ return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q", fmt.Errorf("%s: can't assign %s value to list property %q",
property.Value.Pos, property.Value.Type, parser.List, property.Value.Pos, property.Value.Type, property.Name),
property.Name),
} }
} }
list := []string{} list := make([]string, len(l.Values))
for _, value := range property.Value.ListValue { for i, value := range l.Values {
if value.Type != parser.String { s, ok := value.Eval().(*parser.String)
if !ok {
// The parser should not produce this. // 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)) sliceValue.Set(reflect.ValueOf(list))
@ -327,15 +328,15 @@ func unpackStruct(namePrefix string, structValue reflect.Value,
property *parser.Property, propertyMap map[string]*packedProperty, property *parser.Property, propertyMap map[string]*packedProperty,
filterKey, filterValue string) []error { filterKey, filterValue string) []error {
if property.Value.Type != parser.Map { m, ok := property.Value.Eval().(*parser.Map)
if !ok {
return []error{ return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q", fmt.Errorf("%s: can't assign %s value to map property %q",
property.Value.Pos, property.Value.Type, parser.Map, property.Value.Pos, property.Value.Type, property.Name),
property.Name),
} }
} }
errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap) errs := buildPropertyMap(namePrefix, m.Properties, propertyMap)
if len(errs) > 0 { if len(errs) > 0 {
return errs return errs
} }

View file

@ -27,7 +27,7 @@ import (
var validUnpackTestCases = []struct { var validUnpackTestCases = []struct {
input string input string
output interface{} output []interface{}
errs []error errs []error
}{ }{
{` {`
@ -36,6 +36,7 @@ var validUnpackTestCases = []struct {
blank: "", blank: "",
} }
`, `,
[]interface{}{
struct { struct {
Name *string Name *string
Blank *string Blank *string
@ -45,6 +46,7 @@ var validUnpackTestCases = []struct {
Blank: proptools.StringPtr(""), Blank: proptools.StringPtr(""),
Unset: nil, Unset: nil,
}, },
},
nil, nil,
}, },
@ -53,11 +55,13 @@ var validUnpackTestCases = []struct {
name: "abc", name: "abc",
} }
`, `,
[]interface{}{
struct { struct {
Name string Name string
}{ }{
Name: "abc", Name: "abc",
}, },
},
nil, nil,
}, },
@ -66,11 +70,13 @@ var validUnpackTestCases = []struct {
isGood: true, isGood: true,
} }
`, `,
[]interface{}{
struct { struct {
IsGood bool IsGood bool
}{ }{
IsGood: true, IsGood: true,
}, },
},
nil, nil,
}, },
@ -80,6 +86,7 @@ var validUnpackTestCases = []struct {
isBad: false, isBad: false,
} }
`, `,
[]interface{}{
struct { struct {
IsGood *bool IsGood *bool
IsBad *bool IsBad *bool
@ -89,6 +96,7 @@ var validUnpackTestCases = []struct {
IsBad: proptools.BoolPtr(false), IsBad: proptools.BoolPtr(false),
IsUgly: nil, IsUgly: nil,
}, },
},
nil, nil,
}, },
@ -99,6 +107,7 @@ var validUnpackTestCases = []struct {
empty: [] empty: []
} }
`, `,
[]interface{}{
struct { struct {
Stuff []string Stuff []string
Empty []string Empty []string
@ -108,6 +117,7 @@ var validUnpackTestCases = []struct {
Empty: []string{}, Empty: []string{},
Nil: nil, Nil: nil,
}, },
},
nil, nil,
}, },
@ -118,6 +128,7 @@ var validUnpackTestCases = []struct {
} }
} }
`, `,
[]interface{}{
struct { struct {
Nested struct { Nested struct {
Name string Name string
@ -127,6 +138,7 @@ var validUnpackTestCases = []struct {
Name: "abc", Name: "abc",
}, },
}, },
},
nil, nil,
}, },
@ -137,6 +149,7 @@ var validUnpackTestCases = []struct {
} }
} }
`, `,
[]interface{}{
struct { struct {
Nested interface{} Nested interface{}
}{ }{
@ -144,6 +157,7 @@ var validUnpackTestCases = []struct {
Name: "def", Name: "def",
}, },
}, },
},
nil, nil,
}, },
@ -156,6 +170,7 @@ var validUnpackTestCases = []struct {
baz: ["def", "ghi"], baz: ["def", "ghi"],
} }
`, `,
[]interface{}{
struct { struct {
Nested struct { Nested struct {
Foo string Foo string
@ -169,6 +184,7 @@ var validUnpackTestCases = []struct {
Bar: false, Bar: false,
Baz: []string{"def", "ghi"}, Baz: []string{"def", "ghi"},
}, },
},
nil, nil,
}, },
@ -181,6 +197,7 @@ var validUnpackTestCases = []struct {
baz: ["def", "ghi"], baz: ["def", "ghi"],
} }
`, `,
[]interface{}{
struct { struct {
Nested struct { Nested struct {
Foo string `allowNested:"true"` Foo string `allowNested:"true"`
@ -196,6 +213,7 @@ var validUnpackTestCases = []struct {
Bar: false, Bar: false,
Baz: []string{"def", "ghi"}, Baz: []string{"def", "ghi"},
}, },
},
nil, nil,
}, },
@ -208,6 +226,7 @@ var validUnpackTestCases = []struct {
baz: ["def", "ghi"], baz: ["def", "ghi"],
} }
`, `,
[]interface{}{
struct { struct {
Nested struct { Nested struct {
Foo string Foo string
@ -221,6 +240,7 @@ var validUnpackTestCases = []struct {
Bar: false, Bar: false,
Baz: []string{"def", "ghi"}, Baz: []string{"def", "ghi"},
}, },
},
[]error{ []error{
&Error{ &Error{
Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"), Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
@ -238,6 +258,7 @@ var validUnpackTestCases = []struct {
}, },
} }
`, `,
[]interface{}{
struct { struct {
EmbeddedStruct EmbeddedStruct
Nested struct { Nested struct {
@ -255,6 +276,7 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
},
nil, nil,
}, },
@ -267,6 +289,7 @@ var validUnpackTestCases = []struct {
}, },
} }
`, `,
[]interface{}{
struct { struct {
EmbeddedInterface EmbeddedInterface
Nested struct { Nested struct {
@ -284,6 +307,7 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
},
nil, nil,
}, },
@ -296,6 +320,7 @@ var validUnpackTestCases = []struct {
}, },
} }
`, `,
[]interface{}{
struct { struct {
Name string Name string
EmbeddedStruct EmbeddedStruct
@ -318,6 +343,7 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
},
nil, nil,
}, },
@ -330,6 +356,7 @@ var validUnpackTestCases = []struct {
}, },
} }
`, `,
[]interface{}{
struct { struct {
Name string Name string
EmbeddedInterface EmbeddedInterface
@ -352,6 +379,65 @@ var validUnpackTestCases = []struct {
}, },
}, },
}, },
},
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, nil,
}, },
} }
@ -362,7 +448,7 @@ type EmbeddedInterface interface{}
func TestUnpackProperties(t *testing.T) { func TestUnpackProperties(t *testing.T) {
for _, testCase := range validUnpackTestCases { for _, testCase := range validUnpackTestCases {
r := bytes.NewBufferString(testCase.input) r := bytes.NewBufferString(testCase.input)
file, errs := parser.Parse("", r, nil) file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input) t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected parse errors:") t.Errorf("unexpected parse errors:")
@ -372,10 +458,17 @@ func TestUnpackProperties(t *testing.T) {
t.FailNow() t.FailNow()
} }
module := file.Defs[0].(*parser.Module) for _, def := range file.Defs {
properties := proptools.CloneProperties(reflect.ValueOf(testCase.output)) module, ok := def.(*parser.Module)
proptools.ZeroProperties(properties.Elem()) if !ok {
_, errs = unpackProperties(module.Properties, properties.Interface()) continue
}
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 { if len(errs) != 0 && len(testCase.errs) == 0 {
t.Errorf("test case: %s", testCase.input) t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected unpack errors:") t.Errorf("unexpected unpack errors:")
@ -390,12 +483,20 @@ func TestUnpackProperties(t *testing.T) {
t.Errorf(" got: %+v", errs) t.Errorf(" got: %+v", errs)
} }
output := properties.Elem().Interface() if len(output) != len(testCase.output) {
if !reflect.DeepEqual(output, 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("test case: %s", testCase.input)
t.Errorf("incorrect output:") t.Errorf("incorrect output:")
t.Errorf(" expected: %+v", testCase.output) t.Errorf(" expected: %+v", testCase.output[i])
t.Errorf(" got: %+v", output) t.Errorf(" got: %+v", got)
}
}
} }
} }
} }