Merge remote-tracking branch 'aosp/upstream' into master

3a17686 Merge remote-tracking branch 'aosp/upstream' into master
d4f49b0 Merge pull request #103 from colincross/parser
e32cc80 Refactor blueprint parser nodes to an interface
aedd490 Merge pull request #101 from colincross/doc
7932496 Fix documentation property type for pointers
d9f6fd5 Remove naming stutter in bpdoc
017ed2e Fix govet issues

Change-Id: Ie0863b79ee0d22ae6dbc066ca06303f046d904b7
diff --git a/Blueprints b/Blueprints
index 74db5c1..f3eb843 100644
--- a/Blueprints
+++ b/Blueprints
@@ -32,6 +32,7 @@
     name = "blueprint-parser",
     pkgPath = "github.com/google/blueprint/parser",
     srcs = [
+        "parser/ast.go",
         "parser/modify.go",
         "parser/parser.go",
         "parser/printer.go",
diff --git a/bootstrap/bpdoc/bpdoc.go b/bootstrap/bpdoc/bpdoc.go
index f96d37e..dcb6f65 100644
--- a/bootstrap/bpdoc/bpdoc.go
+++ b/bootstrap/bpdoc/bpdoc.go
@@ -19,97 +19,97 @@
 	"github.com/google/blueprint/proptools"
 )
 
-type DocCollector struct {
+type Context struct {
 	pkgFiles map[string][]string // Map of package name to source files, provided by constructor
 
-	mutex   sync.Mutex
-	pkgDocs map[string]*doc.Package        // Map of package name to parsed Go AST, protected by mutex
-	docs    map[string]*PropertyStructDocs // Map of type name to docs, protected by mutex
+	mutex sync.Mutex
+	pkgs  map[string]*doc.Package    // Map of package name to parsed Go AST, protected by mutex
+	ps    map[string]*PropertyStruct // Map of type name to property struct, protected by mutex
 }
 
-func NewDocCollector(pkgFiles map[string][]string) *DocCollector {
-	return &DocCollector{
+func NewContext(pkgFiles map[string][]string) *Context {
+	return &Context{
 		pkgFiles: pkgFiles,
-		pkgDocs:  make(map[string]*doc.Package),
-		docs:     make(map[string]*PropertyStructDocs),
+		pkgs:     make(map[string]*doc.Package),
+		ps:       make(map[string]*PropertyStruct),
 	}
 }
 
-// Return the PropertyStructDocs associated with a property struct type.  The type should be in the
+// Return the PropertyStruct associated with a property struct type.  The type should be in the
 // format <package path>.<type name>
-func (dc *DocCollector) Docs(pkg, name string, defaults reflect.Value) (*PropertyStructDocs, error) {
-	docs := dc.getDocs(pkg, name)
+func (c *Context) PropertyStruct(pkgPath, name string, defaults reflect.Value) (*PropertyStruct, error) {
+	ps := c.getPropertyStruct(pkgPath, name)
 
-	if docs == nil {
-		pkgDocs, err := dc.packageDocs(pkg)
+	if ps == nil {
+		pkg, err := c.pkg(pkgPath)
 		if err != nil {
 			return nil, err
 		}
 
-		for _, t := range pkgDocs.Types {
+		for _, t := range pkg.Types {
 			if t.Name == name {
-				docs, err = newDocs(t)
+				ps, err = newPropertyStruct(t)
 				if err != nil {
 					return nil, err
 				}
-				docs = dc.putDocs(pkg, name, docs)
+				ps = c.putPropertyStruct(pkgPath, name, ps)
 			}
 		}
 	}
 
-	if docs == nil {
-		return nil, fmt.Errorf("package %q type %q not found", pkg, name)
+	if ps == nil {
+		return nil, fmt.Errorf("package %q type %q not found", pkgPath, name)
 	}
 
-	docs = docs.Clone()
-	docs.SetDefaults(defaults)
+	ps = ps.Clone()
+	ps.SetDefaults(defaults)
 
-	return docs, nil
+	return ps, nil
 }
 
-func (dc *DocCollector) getDocs(pkg, name string) *PropertyStructDocs {
-	dc.mutex.Lock()
-	defer dc.mutex.Unlock()
+func (c *Context) getPropertyStruct(pkgPath, name string) *PropertyStruct {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
 
-	name = pkg + "." + name
+	name = pkgPath + "." + name
 
-	return dc.docs[name]
+	return c.ps[name]
 }
 
-func (dc *DocCollector) putDocs(pkg, name string, docs *PropertyStructDocs) *PropertyStructDocs {
-	dc.mutex.Lock()
-	defer dc.mutex.Unlock()
+func (c *Context) putPropertyStruct(pkgPath, name string, ps *PropertyStruct) *PropertyStruct {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
 
-	name = pkg + "." + name
+	name = pkgPath + "." + name
 
-	if dc.docs[name] != nil {
-		return dc.docs[name]
+	if c.ps[name] != nil {
+		return c.ps[name]
 	} else {
-		dc.docs[name] = docs
-		return docs
+		c.ps[name] = ps
+		return ps
 	}
 }
 
-type PropertyStructDocs struct {
+type PropertyStruct struct {
 	Name       string
 	Text       string
-	Properties []PropertyDocs
+	Properties []Property
 }
 
-type PropertyDocs struct {
+type Property struct {
 	Name       string
 	OtherNames []string
 	Type       string
 	Tag        reflect.StructTag
 	Text       string
 	OtherTexts []string
-	Properties []PropertyDocs
+	Properties []Property
 	Default    string
 }
 
-func (docs *PropertyStructDocs) Clone() *PropertyStructDocs {
-	ret := *docs
-	ret.Properties = append([]PropertyDocs(nil), ret.Properties...)
+func (ps *PropertyStruct) Clone() *PropertyStruct {
+	ret := *ps
+	ret.Properties = append([]Property(nil), ret.Properties...)
 	for i, prop := range ret.Properties {
 		ret.Properties[i] = prop.Clone()
 	}
@@ -117,9 +117,9 @@
 	return &ret
 }
 
-func (docs *PropertyDocs) Clone() PropertyDocs {
-	ret := *docs
-	ret.Properties = append([]PropertyDocs(nil), ret.Properties...)
+func (p *Property) Clone() Property {
+	ret := *p
+	ret.Properties = append([]Property(nil), ret.Properties...)
 	for i, prop := range ret.Properties {
 		ret.Properties[i] = prop.Clone()
 	}
@@ -127,19 +127,19 @@
 	return ret
 }
 
-func (docs *PropertyDocs) Equal(other PropertyDocs) bool {
-	return docs.Name == other.Name && docs.Type == other.Type && docs.Tag == other.Tag &&
-		docs.Text == other.Text && docs.Default == other.Default &&
-		stringArrayEqual(docs.OtherNames, other.OtherNames) &&
-		stringArrayEqual(docs.OtherTexts, other.OtherTexts) &&
-		docs.SameSubProperties(other)
+func (p *Property) Equal(other Property) bool {
+	return p.Name == other.Name && p.Type == other.Type && p.Tag == other.Tag &&
+		p.Text == other.Text && p.Default == other.Default &&
+		stringArrayEqual(p.OtherNames, other.OtherNames) &&
+		stringArrayEqual(p.OtherTexts, other.OtherTexts) &&
+		p.SameSubProperties(other)
 }
 
-func (docs *PropertyStructDocs) SetDefaults(defaults reflect.Value) {
-	setDefaults(docs.Properties, defaults)
+func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) {
+	setDefaults(ps.Properties, defaults)
 }
 
-func setDefaults(properties []PropertyDocs, defaults reflect.Value) {
+func setDefaults(properties []Property, defaults reflect.Value) {
 	for i := range properties {
 		prop := &properties[i]
 		fieldName := proptools.FieldNameForProperty(prop.Name)
@@ -182,13 +182,13 @@
 	return true
 }
 
-func (docs *PropertyDocs) SameSubProperties(other PropertyDocs) bool {
-	if len(docs.Properties) != len(other.Properties) {
+func (p *Property) SameSubProperties(other Property) bool {
+	if len(p.Properties) != len(other.Properties) {
 		return false
 	}
 
-	for i := range docs.Properties {
-		if !docs.Properties[i].Equal(other.Properties[i]) {
+	for i := range p.Properties {
+		if !p.Properties[i].Equal(other.Properties[i]) {
 			return false
 		}
 	}
@@ -196,11 +196,11 @@
 	return true
 }
 
-func (docs *PropertyStructDocs) GetByName(name string) *PropertyDocs {
-	return getByName(name, "", &docs.Properties)
+func (ps *PropertyStruct) GetByName(name string) *Property {
+	return getByName(name, "", &ps.Properties)
 }
 
-func getByName(name string, prefix string, props *[]PropertyDocs) *PropertyDocs {
+func getByName(name string, prefix string, props *[]Property) *Property {
 	for i := range *props {
 		if prefix+(*props)[i].Name == name {
 			return &(*props)[i]
@@ -211,15 +211,15 @@
 	return nil
 }
 
-func (prop *PropertyDocs) Nest(nested *PropertyStructDocs) {
-	//prop.Name += "(" + nested.Name + ")"
-	//prop.Text += "(" + nested.Text + ")"
-	prop.Properties = append(prop.Properties, nested.Properties...)
+func (p *Property) Nest(nested *PropertyStruct) {
+	//p.Name += "(" + nested.Name + ")"
+	//p.Text += "(" + nested.Text + ")"
+	p.Properties = append(p.Properties, nested.Properties...)
 }
 
-func newDocs(t *doc.Type) (*PropertyStructDocs, error) {
+func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) {
 	typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
-	docs := PropertyStructDocs{
+	ps := PropertyStruct{
 		Name: t.Name,
 		Text: t.Doc,
 	}
@@ -230,15 +230,15 @@
 	}
 
 	var err error
-	docs.Properties, err = structProperties(structType)
+	ps.Properties, err = structProperties(structType)
 	if err != nil {
 		return nil, err
 	}
 
-	return &docs, nil
+	return &ps, nil
 }
 
-func structProperties(structType *ast.StructType) (props []PropertyDocs, err error) {
+func structProperties(structType *ast.StructType) (props []Property, err error) {
 	for _, f := range structType.Fields.List {
 		names := f.Names
 		if names == nil {
@@ -250,7 +250,7 @@
 		}
 		for _, n := range names {
 			var name, typ, tag, text string
-			var innerProps []PropertyDocs
+			var innerProps []Property
 			if n != nil {
 				name = proptools.PropertyNameForField(n.Name)
 			}
@@ -263,7 +263,12 @@
 					return nil, err
 				}
 			}
-			switch a := f.Type.(type) {
+
+			t := f.Type
+			if star, ok := t.(*ast.StarExpr); ok {
+				t = star.X
+			}
+			switch a := t.(type) {
 			case *ast.ArrayType:
 				typ = "list of strings"
 			case *ast.InterfaceType:
@@ -279,7 +284,7 @@
 				typ = fmt.Sprintf("%T", f.Type)
 			}
 
-			props = append(props, PropertyDocs{
+			props = append(props, Property{
 				Name:       name,
 				Type:       typ,
 				Tag:        reflect.StructTag(tag),
@@ -292,15 +297,15 @@
 	return props, nil
 }
 
-func (docs *PropertyStructDocs) ExcludeByTag(key, value string) {
-	filterPropsByTag(&docs.Properties, key, value, true)
+func (ps *PropertyStruct) ExcludeByTag(key, value string) {
+	filterPropsByTag(&ps.Properties, key, value, true)
 }
 
-func (docs *PropertyStructDocs) IncludeByTag(key, value string) {
-	filterPropsByTag(&docs.Properties, key, value, false)
+func (ps *PropertyStruct) IncludeByTag(key, value string) {
+	filterPropsByTag(&ps.Properties, key, value, false)
 }
 
-func filterPropsByTag(props *[]PropertyDocs, key, value string, exclude bool) {
+func filterPropsByTag(props *[]Property, key, value string, exclude bool) {
 	// Create a slice that shares the storage of props but has 0 length.  Appending up to
 	// len(props) times to this slice will overwrite the original slice contents
 	filtered := (*props)[:0]
@@ -317,40 +322,40 @@
 }
 
 // Package AST generation and storage
-func (dc *DocCollector) packageDocs(pkg string) (*doc.Package, error) {
-	pkgDocs := dc.getPackageDocs(pkg)
-	if pkgDocs == nil {
-		if files, ok := dc.pkgFiles[pkg]; ok {
+func (c *Context) pkg(pkgPath string) (*doc.Package, error) {
+	pkg := c.getPackage(pkgPath)
+	if pkg == nil {
+		if files, ok := c.pkgFiles[pkgPath]; ok {
 			var err error
 			pkgAST, err := NewPackageAST(files)
 			if err != nil {
 				return nil, err
 			}
-			pkgDocs = doc.New(pkgAST, pkg, doc.AllDecls)
-			pkgDocs = dc.putPackageDocs(pkg, pkgDocs)
+			pkg = doc.New(pkgAST, pkgPath, doc.AllDecls)
+			pkg = c.putPackage(pkgPath, pkg)
 		} else {
-			return nil, fmt.Errorf("unknown package %q", pkg)
+			return nil, fmt.Errorf("unknown package %q", pkgPath)
 		}
 	}
-	return pkgDocs, nil
+	return pkg, nil
 }
 
-func (dc *DocCollector) getPackageDocs(pkg string) *doc.Package {
-	dc.mutex.Lock()
-	defer dc.mutex.Unlock()
+func (c *Context) getPackage(pkgPath string) *doc.Package {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
 
-	return dc.pkgDocs[pkg]
+	return c.pkgs[pkgPath]
 }
 
-func (dc *DocCollector) putPackageDocs(pkg string, pkgDocs *doc.Package) *doc.Package {
-	dc.mutex.Lock()
-	defer dc.mutex.Unlock()
+func (c *Context) putPackage(pkgPath string, pkg *doc.Package) *doc.Package {
+	c.mutex.Lock()
+	defer c.mutex.Unlock()
 
-	if dc.pkgDocs[pkg] != nil {
-		return dc.pkgDocs[pkg]
+	if c.pkgs[pkgPath] != nil {
+		return c.pkgs[pkgPath]
 	} else {
-		dc.pkgDocs[pkg] = pkgDocs
-		return pkgDocs
+		c.pkgs[pkgPath] = pkg
+		return pkg
 	}
 }
 
@@ -373,19 +378,19 @@
 func Write(filename string, pkgFiles map[string][]string,
 	moduleTypePropertyStructs map[string][]interface{}) error {
 
-	docSet := NewDocCollector(pkgFiles)
+	c := NewContext(pkgFiles)
 
-	var moduleTypeList []*moduleTypeDoc
+	var moduleTypeList []*moduleType
 	for moduleType, propertyStructs := range moduleTypePropertyStructs {
-		mtDoc, err := getModuleTypeDoc(docSet, moduleType, propertyStructs)
+		mt, err := getModuleType(c, moduleType, propertyStructs)
 		if err != nil {
 			return err
 		}
-		removeEmptyPropertyStructs(mtDoc)
-		collapseDuplicatePropertyStructs(mtDoc)
-		collapseNestedPropertyStructs(mtDoc)
-		combineDuplicateProperties(mtDoc)
-		moduleTypeList = append(moduleTypeList, mtDoc)
+		removeEmptyPropertyStructs(mt)
+		collapseDuplicatePropertyStructs(mt)
+		collapseNestedPropertyStructs(mt)
+		combineDuplicateProperties(mt)
+		moduleTypeList = append(moduleTypeList, mt)
 	}
 
 	sort.Sort(moduleTypeByName(moduleTypeList))
@@ -416,11 +421,11 @@
 	return nil
 }
 
-func getModuleTypeDoc(docSet *DocCollector, moduleType string,
-	propertyStructs []interface{}) (*moduleTypeDoc, error) {
-	mtDoc := &moduleTypeDoc{
-		Name: moduleType,
-		//Text: docSet.ModuleTypeDocs(moduleType),
+func getModuleType(c *Context, moduleTypeName string,
+	propertyStructs []interface{}) (*moduleType, error) {
+	mt := &moduleType{
+		Name: moduleTypeName,
+		//Text: c.ModuleTypeDocs(moduleType),
 	}
 
 	for _, s := range propertyStructs {
@@ -431,27 +436,27 @@
 		if t.PkgPath() == "" {
 			continue
 		}
-		psDoc, err := docSet.Docs(t.PkgPath(), t.Name(), v)
+		ps, err := c.PropertyStruct(t.PkgPath(), t.Name(), v)
 		if err != nil {
 			return nil, err
 		}
-		psDoc.ExcludeByTag("blueprint", "mutated")
+		ps.ExcludeByTag("blueprint", "mutated")
 
-		for nested, nestedValue := range nestedPropertyStructs(v) {
+		for nestedName, nestedValue := range nestedPropertyStructs(v) {
 			nestedType := nestedValue.Type()
 
 			// Ignore property structs with unexported or unnamed types
 			if nestedType.PkgPath() == "" {
 				continue
 			}
-			nestedDoc, err := docSet.Docs(nestedType.PkgPath(), nestedType.Name(), nestedValue)
+			nested, err := c.PropertyStruct(nestedType.PkgPath(), nestedType.Name(), nestedValue)
 			if err != nil {
 				return nil, err
 			}
-			nestedDoc.ExcludeByTag("blueprint", "mutated")
-			nestPoint := psDoc.GetByName(nested)
+			nested.ExcludeByTag("blueprint", "mutated")
+			nestPoint := ps.GetByName(nestedName)
 			if nestPoint == nil {
-				return nil, fmt.Errorf("nesting point %q not found", nested)
+				return nil, fmt.Errorf("nesting point %q not found", nestedName)
 			}
 
 			key, value, err := blueprint.HasFilter(nestPoint.Tag)
@@ -459,15 +464,15 @@
 				return nil, err
 			}
 			if key != "" {
-				nestedDoc.IncludeByTag(key, value)
+				nested.IncludeByTag(key, value)
 			}
 
-			nestPoint.Nest(nestedDoc)
+			nestPoint.Nest(nested)
 		}
-		mtDoc.PropertyStructs = append(mtDoc.PropertyStructs, psDoc)
+		mt.PropertyStructs = append(mt.PropertyStructs, ps)
 	}
 
-	return mtDoc, nil
+	return mt, nil
 }
 
 func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value {
@@ -520,33 +525,33 @@
 }
 
 // Remove any property structs that have no exported fields
-func removeEmptyPropertyStructs(mtDoc *moduleTypeDoc) {
-	for i := 0; i < len(mtDoc.PropertyStructs); i++ {
-		if len(mtDoc.PropertyStructs[i].Properties) == 0 {
-			mtDoc.PropertyStructs = append(mtDoc.PropertyStructs[:i], mtDoc.PropertyStructs[i+1:]...)
+func removeEmptyPropertyStructs(mt *moduleType) {
+	for i := 0; i < len(mt.PropertyStructs); i++ {
+		if len(mt.PropertyStructs[i].Properties) == 0 {
+			mt.PropertyStructs = append(mt.PropertyStructs[:i], mt.PropertyStructs[i+1:]...)
 			i--
 		}
 	}
 }
 
 // Squashes duplicates of the same property struct into single entries
-func collapseDuplicatePropertyStructs(mtDoc *moduleTypeDoc) {
-	var collapsedDocs []*PropertyStructDocs
+func collapseDuplicatePropertyStructs(mt *moduleType) {
+	var collapsed []*PropertyStruct
 
 propertyStructLoop:
-	for _, from := range mtDoc.PropertyStructs {
-		for _, to := range collapsedDocs {
+	for _, from := range mt.PropertyStructs {
+		for _, to := range collapsed {
 			if from.Name == to.Name {
 				collapseDuplicateProperties(&to.Properties, &from.Properties)
 				continue propertyStructLoop
 			}
 		}
-		collapsedDocs = append(collapsedDocs, from)
+		collapsed = append(collapsed, from)
 	}
-	mtDoc.PropertyStructs = collapsedDocs
+	mt.PropertyStructs = collapsed
 }
 
-func collapseDuplicateProperties(to, from *[]PropertyDocs) {
+func collapseDuplicateProperties(to, from *[]Property) {
 propertyLoop:
 	for _, f := range *from {
 		for i := range *to {
@@ -562,14 +567,14 @@
 
 // Find all property structs that only contain structs, and move their children up one with
 // a prefixed name
-func collapseNestedPropertyStructs(mtDoc *moduleTypeDoc) {
-	for _, ps := range mtDoc.PropertyStructs {
+func collapseNestedPropertyStructs(mt *moduleType) {
+	for _, ps := range mt.PropertyStructs {
 		collapseNestedProperties(&ps.Properties)
 	}
 }
 
-func collapseNestedProperties(p *[]PropertyDocs) {
-	var n []PropertyDocs
+func collapseNestedProperties(p *[]Property) {
+	var n []Property
 
 	for _, parent := range *p {
 		var containsProperty bool
@@ -594,14 +599,14 @@
 	*p = n
 }
 
-func combineDuplicateProperties(mtDoc *moduleTypeDoc) {
-	for _, ps := range mtDoc.PropertyStructs {
+func combineDuplicateProperties(mt *moduleType) {
+	for _, ps := range mt.PropertyStructs {
 		combineDuplicateSubProperties(&ps.Properties)
 	}
 }
 
-func combineDuplicateSubProperties(p *[]PropertyDocs) {
-	var n []PropertyDocs
+func combineDuplicateSubProperties(p *[]Property) {
+	var n []Property
 propertyLoop:
 	for _, child := range *p {
 		if len(child.Properties) > 0 {
@@ -621,16 +626,16 @@
 	*p = n
 }
 
-type moduleTypeByName []*moduleTypeDoc
+type moduleTypeByName []*moduleType
 
 func (l moduleTypeByName) Len() int           { return len(l) }
 func (l moduleTypeByName) Less(i, j int) bool { return l[i].Name < l[j].Name }
 func (l moduleTypeByName) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
 
-type moduleTypeDoc struct {
+type moduleType struct {
 	Name            string
 	Text            string
-	PropertyStructs []*PropertyStructDocs
+	PropertyStructs []*PropertyStruct
 }
 
 var (
diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go
index f4216dd..6f7a5bf 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -9,7 +9,6 @@
 	"bytes"
 	"flag"
 	"fmt"
-	"github.com/google/blueprint/parser"
 	"io"
 	"io/ioutil"
 	"os"
@@ -17,6 +16,8 @@
 	"path/filepath"
 	"strings"
 	"unicode"
+
+	"github.com/google/blueprint/parser"
 )
 
 var (
@@ -123,8 +124,8 @@
 	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 @@
 
 	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 @@
 	return false, nil
 }
 
-func processParameter(value *parser.Value, paramName, moduleName string,
+func processParameter(value parser.Expression, paramName, moduleName string,
 	file *parser.File) (modified bool, errs []error) {
-	if value.Type != parser.List {
-		return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
-			paramName, moduleName, value.Type.String())}
-	}
-
-	if value.Variable != "" {
+	if _, ok := value.(*parser.Variable); ok {
 		return false, []error{fmt.Errorf("parameter %s in module %s is a variable, unsupported",
 			paramName, moduleName)}
 	}
 
-	if value.Expression != nil {
+	if _, ok := value.(*parser.Operator); ok {
 		return false, []error{fmt.Errorf("parameter %s in module %s is an expression, unsupported",
 			paramName, moduleName)}
 	}
 
-	wasSorted := parser.ListIsSorted(*value)
+	list, ok := value.(*parser.List)
+	if !ok {
+		return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
+			paramName, moduleName, value.Type().String())}
+	}
+
+	wasSorted := parser.ListIsSorted(list)
 
 	for _, a := range addIdents.idents {
-		m := parser.AddStringToList(value, a)
+		m := parser.AddStringToList(list, a)
 		modified = modified || m
 	}
 
 	for _, r := range removeIdents.idents {
-		m := parser.RemoveStringFromList(value, r)
+		m := parser.RemoveStringFromList(list, r)
 		modified = modified || m
 	}
 
 	if (wasSorted || *sortLists) && modified {
-		parser.SortList(file, *value)
+		parser.SortList(file, list)
 	}
 
 	return modified, nil
diff --git a/build.ninja.in b/build.ninja.in
index 9ba88c8..b70ea22 100644
--- a/build.ninja.in
+++ b/build.ninja.in
@@ -81,7 +81,7 @@
 # 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 @@
 # 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 @@
 # 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 @@
 
 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 @@
 # 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 @@
 # 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 @@
 # 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 @@
 # 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 @@
 # 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 @@
 # 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 | $
diff --git a/context.go b/context.go
index dde4734..b644ac5 100644
--- a/context.go
+++ b/context.go
@@ -861,21 +861,22 @@
 	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 @@
 	if assignment, _ := scope.Get(v); assignment == nil {
 		return "", scanner.Position{}, nil
 	} else {
-		switch assignment.Value.Type {
-		case parser.String:
-			return assignment.Value.StringValue, assignment.Pos, nil
-		case parser.Bool, parser.List:
+		switch value := assignment.Value.Eval().(type) {
+		case *parser.String:
+			return value.Value, assignment.Pos, nil
+		case *parser.Bool, *parser.List:
 			return "", scanner.Position{}, &Error{
 				Err: fmt.Errorf("%q must be a string", v),
 				Pos: assignment.Pos,
diff --git a/package_ctx.go b/package_ctx.go
index cedee04..8e27150 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -96,7 +96,7 @@
 	checkCalledFromInit()
 
 	if _, present := packageContexts[pkgPath]; present {
-		panic(fmt.Errorf("package %q already has a package context"))
+		panic(fmt.Errorf("package %q already has a package context", pkgPath))
 	}
 
 	pkgName := pkgPathToName(pkgPath)
diff --git a/parser/ast.go b/parser/ast.go
new file mode 100644
index 0000000..387f6d5
--- /dev/null
+++ b/parser/ast.go
@@ -0,0 +1,374 @@
+// Copyright 2016 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package parser
+
+import (
+	"fmt"
+	"strings"
+	"text/scanner"
+)
+
+// Definition is an Assignment or a Module at the top level of a Blueprints file
+type Definition interface {
+	String() string
+	definitionTag()
+}
+
+// An Assignment is a variable assignment at the top level of a Blueprints file, scoped to the
+// file and and subdirs.
+type Assignment struct {
+	Name       Ident
+	Value      Expression
+	OrigValue  Expression
+	Pos        scanner.Position
+	Assigner   string
+	Referenced bool
+}
+
+func (a *Assignment) String() string {
+	return fmt.Sprintf("%s@%s %s %s (%s) %t", a.Name, a.Pos, a.Assigner, a.Value, a.OrigValue, a.Referenced)
+}
+
+func (a *Assignment) definitionTag() {}
+
+// A Module is a module definition at the top level of a Blueprints file
+type Module struct {
+	Type Ident
+	Map
+}
+
+func (m *Module) Copy() *Module {
+	ret := *m
+	ret.Properties = make([]*Property, len(m.Properties))
+	for i := range m.Properties {
+		ret.Properties[i] = m.Properties[i].Copy()
+	}
+	return &ret
+}
+
+func (m *Module) String() string {
+	propertyStrings := make([]string, len(m.Properties))
+	for i, property := range m.Properties {
+		propertyStrings[i] = property.String()
+	}
+	return fmt.Sprintf("%s@%s-%s{%s}", m.Type,
+		m.LBracePos, m.RBracePos,
+		strings.Join(propertyStrings, ", "))
+}
+
+func (m *Module) definitionTag() {}
+
+// A Property is a name: value pair within a Map, which may be a top level Module.
+type Property struct {
+	Name  Ident
+	Value Expression
+	Pos   scanner.Position
+}
+
+func (p *Property) Copy() *Property {
+	ret := *p
+	ret.Value = p.Value.Copy()
+	return &ret
+}
+
+func (p *Property) String() string {
+	return fmt.Sprintf("%s@%s: %s", p.Name, p.Pos, p.Value)
+}
+
+// An Ident is a name identifier, the Type of a Module, the Name of a Property, or the Name of a
+// Variable.
+type Ident struct {
+	Name string
+	Pos  scanner.Position
+}
+
+func (i Ident) String() string {
+	return fmt.Sprintf("%s@%s", i.Name, i.Pos)
+}
+
+// An Expression is a Value in a Property or Assignment.  It can be a literal (String or Bool), a
+// Map, a List, an Operator that combines two expressions of the same type, or a Variable that
+// references and Assignment.
+type Expression interface {
+	// Copy returns a copy of the Expression that will not affect the original if mutated
+	Copy() Expression
+	String() string
+	// Type returns the underlying Type enum of the Expression if it were to be evalutated
+	Type() Type
+	// Pos returns the position of the first token in the Expression
+	Pos() scanner.Position
+	// End returns the position of the beginning of the last token in the Expression
+	End() scanner.Position
+	// Eval returns an expression that is fully evaluated to a simple type (List, Map, String, or
+	// Bool).  It will return the same object for every call to Eval().
+	Eval() Expression
+}
+
+type Type int
+
+const (
+	BoolType Type = iota + 1
+	StringType
+	ListType
+	MapType
+)
+
+func (t Type) String() string {
+	switch t {
+	case BoolType:
+		return "bool"
+	case StringType:
+		return "string"
+	case ListType:
+		return "list"
+	case MapType:
+		return "map"
+	default:
+		panic(fmt.Errorf("Unknown type %d", t))
+	}
+}
+
+type Operator struct {
+	Args        [2]Expression
+	Operator    rune
+	OperatorPos scanner.Position
+	Value       Expression
+}
+
+func (x *Operator) Copy() Expression {
+	ret := *x
+	ret.Args[0] = x.Args[0].Copy()
+	ret.Args[1] = x.Args[1].Copy()
+	return &ret
+}
+
+func (x *Operator) Eval() Expression {
+	return x.Value.Eval()
+}
+
+func (x *Operator) Type() Type {
+	return x.Args[0].Type()
+}
+
+func (x *Operator) Pos() scanner.Position { return x.Args[0].Pos() }
+func (x *Operator) End() scanner.Position { return x.Args[1].End() }
+
+func (x *Operator) String() string {
+	return fmt.Sprintf("(%s %c %s = %s)@%s", x.Args[0].String(), x.Operator, x.Args[1].String(),
+		x.Value, x.OperatorPos)
+}
+
+type Variable struct {
+	Name    string
+	Value   Expression
+	NamePos scanner.Position
+}
+
+func (x *Variable) Pos() scanner.Position { return x.NamePos }
+func (x *Variable) End() scanner.Position { return x.NamePos }
+
+func (x *Variable) Copy() Expression {
+	ret := *x
+	return &ret
+}
+
+func (x *Variable) Eval() Expression {
+	return x.Value.Eval()
+}
+
+func (x *Variable) String() string {
+	return x.Name + " = " + x.Value.String()
+}
+
+func (x *Variable) Type() Type { return x.Value.Type() }
+
+type Map struct {
+	LBracePos  scanner.Position
+	RBracePos  scanner.Position
+	Properties []*Property
+}
+
+func (x *Map) Pos() scanner.Position { return x.LBracePos }
+func (x *Map) End() scanner.Position { return x.RBracePos }
+
+func (x *Map) Copy() Expression {
+	ret := *x
+	ret.Properties = make([]*Property, len(x.Properties))
+	for i := range x.Properties {
+		ret.Properties[i] = x.Properties[i].Copy()
+	}
+	return &ret
+}
+
+func (x *Map) Eval() Expression {
+	return x
+}
+
+func (x *Map) String() string {
+	propertyStrings := make([]string, len(x.Properties))
+	for i, property := range x.Properties {
+		propertyStrings[i] = property.String()
+	}
+	return fmt.Sprintf("@%s-%s{%s}", x.LBracePos, x.RBracePos,
+		strings.Join(propertyStrings, ", "))
+}
+
+func (x *Map) Type() Type { return MapType }
+
+type List struct {
+	LBracePos scanner.Position
+	RBracePos scanner.Position
+	Values    []Expression
+}
+
+func (x *List) Pos() scanner.Position { return x.LBracePos }
+func (x *List) End() scanner.Position { return x.RBracePos }
+
+func (x *List) Copy() Expression {
+	ret := *x
+	ret.Values = make([]Expression, len(x.Values))
+	for i := range ret.Values {
+		ret.Values[i] = x.Values[i].Copy()
+	}
+	return &ret
+}
+
+func (x *List) Eval() Expression {
+	return x
+}
+
+func (x *List) String() string {
+	valueStrings := make([]string, len(x.Values))
+	for i, value := range x.Values {
+		valueStrings[i] = value.String()
+	}
+	return fmt.Sprintf("@%s-%s[%s]", x.LBracePos, x.RBracePos,
+		strings.Join(valueStrings, ", "))
+}
+
+func (x *List) Type() Type { return ListType }
+
+type String struct {
+	LiteralPos scanner.Position
+	Value      string
+}
+
+func (x *String) Pos() scanner.Position { return x.LiteralPos }
+func (x *String) End() scanner.Position { return x.LiteralPos }
+
+func (x *String) Copy() Expression {
+	ret := *x
+	return &ret
+}
+
+func (x *String) Eval() Expression {
+	return x
+}
+
+func (x *String) String() string {
+	return fmt.Sprintf("%q@%s", x.Value, x.LiteralPos)
+}
+
+func (x *String) Type() Type {
+	return StringType
+}
+
+type Bool struct {
+	LiteralPos scanner.Position
+	Value      bool
+}
+
+func (x *Bool) Pos() scanner.Position { return x.LiteralPos }
+func (x *Bool) End() scanner.Position { return x.LiteralPos }
+
+func (x *Bool) Copy() Expression {
+	ret := *x
+	return &ret
+}
+
+func (x *Bool) Eval() Expression {
+	return x
+}
+
+func (x *Bool) String() string {
+	return fmt.Sprintf("%t@%s", x.Value, x.LiteralPos)
+}
+
+func (x *Bool) Type() Type {
+	return BoolType
+}
+
+type Comment struct {
+	Comment []string
+	Slash   scanner.Position
+}
+
+func (c Comment) Pos() scanner.Position {
+	return c.Slash
+}
+
+func (c Comment) End() scanner.Position {
+	pos := c.Slash
+	for _, comment := range c.Comment {
+		pos.Offset += len(comment)
+	}
+	pos.Line += len(c.Comment) - 1
+	return pos
+}
+
+func (c Comment) String() string {
+	l := 0
+	for _, comment := range c.Comment {
+		l += len(comment) + 1
+	}
+	buf := make([]byte, 0, l)
+	for _, comment := range c.Comment {
+		buf = append(buf, comment...)
+		buf = append(buf, '\n')
+	}
+
+	return string(buf) + "@" + c.Slash.String()
+}
+
+// Return the text of the comment with // or /* and */ stripped
+func (c Comment) Text() string {
+	l := 0
+	for _, comment := range c.Comment {
+		l += len(comment) + 1
+	}
+	buf := make([]byte, 0, l)
+
+	blockComment := false
+	if strings.HasPrefix(c.Comment[0], "/*") {
+		blockComment = true
+	}
+
+	for i, comment := range c.Comment {
+		if blockComment {
+			if i == 0 {
+				comment = strings.TrimPrefix(comment, "/*")
+			}
+			if i == len(c.Comment)-1 {
+				comment = strings.TrimSuffix(comment, "*/")
+			}
+		} else {
+			comment = strings.TrimPrefix(comment, "//")
+		}
+		buf = append(buf, comment...)
+		buf = append(buf, '\n')
+	}
+
+	return string(buf)
+}
diff --git a/parser/modify.go b/parser/modify.go
index 1b11e2c..08a3f3f 100644
--- a/parser/modify.go
+++ b/parser/modify.go
@@ -14,47 +14,38 @@
 
 package parser
 
-func AddStringToList(value *Value, s string) (modified bool) {
-	if value.Type != List {
-		panic("expected list value, got " + value.Type.String())
-	}
+import "fmt"
 
-	for _, v := range value.ListValue {
-		if v.Type != String {
-			panic("expected string in list, got " + value.Type.String())
+func AddStringToList(list *List, s string) (modified bool) {
+	for _, v := range list.Values {
+		if v.Type() != StringType {
+			panic(fmt.Errorf("expected string in list, got %s", v.Type()))
 		}
 
-		if v.StringValue == s {
+		if sv, ok := v.(*String); ok && sv.Value == s {
 			// string already exists
 			return false
 		}
-
 	}
 
-	value.ListValue = append(value.ListValue, Value{
-		Type:        String,
-		Pos:         value.EndPos,
-		StringValue: s,
+	list.Values = append(list.Values, &String{
+		LiteralPos: list.RBracePos,
+		Value:      s,
 	})
 
 	return true
 }
 
-func RemoveStringFromList(value *Value, s string) (modified bool) {
-	if value.Type != List {
-		panic("expected list value, got " + value.Type.String())
-	}
-
-	for i, v := range value.ListValue {
-		if v.Type != String {
-			panic("expected string in list, got " + value.Type.String())
+func RemoveStringFromList(list *List, s string) (modified bool) {
+	for i, v := range list.Values {
+		if v.Type() != StringType {
+			panic(fmt.Errorf("expected string in list, got %s", v.Type()))
 		}
 
-		if v.StringValue == s {
-			value.ListValue = append(value.ListValue[:i], value.ListValue[i+1:]...)
+		if sv, ok := v.(*String); ok && sv.Value == s {
+			list.Values = append(list.Values[:i], list.Values[i+1:]...)
 			return true
 		}
-
 	}
 
 	return false
diff --git a/parser/parser.go b/parser/parser.go
index fb931af..6909c50 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -41,6 +41,7 @@
 	Name     string
 	Defs     []Definition
 	Comments []Comment
+	Lines    []scanner.Position
 }
 
 func parse(p *parser) (file *File, errs []error) {
@@ -178,8 +179,8 @@
 	}
 }
 
-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 @@
 	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 @@
 	}
 
 	if !p.accept(p.tok) {
-		return
+		return nil
 	}
 	properties := p.parsePropertyList(true, compat)
 	rbracePos := p.scanner.Position
@@ -244,11 +243,14 @@
 		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 @@
 	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) 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 @@
 	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 @@
 	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) 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 @@
 				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.StringValue = str
+
+	value := &String{
+		LiteralPos: p.scanner.Position,
+		Value:      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 @@
 		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 @@
 
 	return strings.Join(ret, "\n")
 }
-
-type Comment struct {
-	Comment []string
-	Pos     scanner.Position
-}
-
-func (c Comment) String() string {
-	l := 0
-	for _, comment := range c.Comment {
-		l += len(comment) + 1
-	}
-	buf := make([]byte, 0, l)
-	for _, comment := range c.Comment {
-		buf = append(buf, comment...)
-		buf = append(buf, '\n')
-	}
-
-	return string(buf)
-}
-
-// Return the text of the comment with // or /* and */ stripped
-func (c Comment) Text() string {
-	l := 0
-	for _, comment := range c.Comment {
-		l += len(comment) + 1
-	}
-	buf := make([]byte, 0, l)
-
-	blockComment := false
-	if strings.HasPrefix(c.Comment[0], "/*") {
-		blockComment = true
-	}
-
-	for i, comment := range c.Comment {
-		if blockComment {
-			if i == 0 {
-				comment = strings.TrimPrefix(comment, "/*")
-			}
-			if i == len(c.Comment)-1 {
-				comment = strings.TrimSuffix(comment, "*/")
-			}
-		} else {
-			comment = strings.TrimPrefix(comment, "//")
-		}
-		buf = append(buf, comment...)
-		buf = append(buf, '\n')
-	}
-
-	return string(buf)
-}
-
-// Return the line number that the comment ends on
-func (c Comment) EndLine() int {
-	return c.Pos.Line + len(c.Comment) - 1
-}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 8925684..e93bb09 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -39,9 +39,11 @@
 		`,
 		[]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 @@
 		`,
 		[]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 @@
 		`,
 		[]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 @@
 		`,
 		[]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 @@
 		`,
 		[]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 @@
 
 	{`
 		// 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 @@
 		[]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 @@
 		`,
 		[]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 @@
 			&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 @@
 			&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 @@
 			&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",
 							},
 						},
-						Operator: '+',
-						Pos:      mkpos(41, 4, 13),
+						&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",
+								},
+							},
+						},
 					},
 				},
-				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",
 							},
 						},
-						Operator: '+',
-						Pos:      mkpos(41, 4, 13),
+						&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:   "=",
@@ -385,69 +407,90 @@
 			&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",
 										},
 									},
-									Operator: '+',
-									Pos:      mkpos(41, 4, 13),
+									&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",
+											},
+										},
+									},
 								},
 							},
-							{
-								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 @@
 			&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 @@
 
 		if len(file.Comments) == len(testCase.comments) {
 			for i := range file.Comments {
-				if !reflect.DeepEqual(file.Comments, testCase.comments) {
+				if !reflect.DeepEqual(file.Comments[i], testCase.comments[i]) {
 					t.Errorf("test case: %s", testCase.input)
 					t.Errorf("incorrect comment %d:", i)
 					t.Errorf("  expected: %s", testCase.comments[i])
diff --git a/parser/printer.go b/parser/printer.go
index b27f5e0..1e7bc2a 100644
--- a/parser/printer.go
+++ b/parser/printer.go
@@ -22,7 +22,7 @@
 	"unicode"
 )
 
-var noPos = scanner.Position{}
+var noPos scanner.Position
 
 type printer struct {
 	defs     []Definition
@@ -91,96 +91,94 @@
 	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 @@
 
 // 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 @@
 // 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 @@
 
 // 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 @@
 // Print any comments that occur after the last token, and a trailing newline
 func (p *printer) flush() {
 	for _, c := range p.skippedComments {
-		if !p.requestNewlinesForPos(c.Pos) {
+		if !p.requestNewlinesForPos(c.Slash) {
 			p.requestSpace()
 		}
 		p.printComment(c)
 	}
 	for p.curComment < len(p.comments) {
 		c := p.comments[p.curComment]
-		if !p.requestNewlinesForPos(c.Pos) {
+		if !p.requestNewlinesForPos(c.Slash) {
 			p.requestSpace()
 		}
 		p.printComment(c)
diff --git a/parser/printer_test.go b/parser/printer_test.go
index e5acf06..3e759cb 100644
--- a/parser/printer_test.go
+++ b/parser/printer_test.go
@@ -62,6 +62,49 @@
 	},
 	{
 		input: `
+		        var = "asdf"
+			foo {
+				stuff: ["asdf"] + var,
+			}`,
+		output: `
+var = "asdf"
+foo {
+    stuff: ["asdf"] + var,
+}
+`,
+	},
+	{
+		input: `
+		        var = "asdf"
+			foo {
+				stuff: [
+				    "asdf"
+				] + var,
+			}`,
+		output: `
+var = "asdf"
+foo {
+    stuff: [
+        "asdf",
+    ] + var,
+}
+`,
+	},
+	{
+		input: `
+		        var = "asdf"
+			foo {
+				stuff: ["asdf"] + var + ["qwert"],
+			}`,
+		output: `
+var = "asdf"
+foo {
+    stuff: ["asdf"] + var + ["qwert"],
+}
+`,
+	},
+	{
+		input: `
 		foo {
 			stuff: {
 				isGood: true,
diff --git a/parser/sort.go b/parser/sort.go
index 381ef82..05ce5fd 100644
--- a/parser/sort.go
+++ b/parser/sort.go
@@ -32,40 +32,40 @@
 	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 @@
 	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 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) 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) {
diff --git a/unpack.go b/unpack.go
index b023956..11718b4 100644
--- a/unpack.go
+++ b/unpack.go
@@ -88,7 +88,6 @@
 				// 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 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 @@
 	property *parser.Property, propertyMap map[string]*packedProperty,
 	filterKey, filterValue string) []error {
 
-	if property.Value.Type != parser.Map {
+	m, ok := property.Value.Eval().(*parser.Map)
+	if !ok {
 		return []error{
-			fmt.Errorf("%s: can't assign %s value to %s property %q",
-				property.Value.Pos, property.Value.Type, parser.Map,
-				property.Name),
+			fmt.Errorf("%s: can't assign %s value to map property %q",
+				property.Value.Pos, property.Value.Type, property.Name),
 		}
 	}
 
-	errs := buildPropertyMap(namePrefix, property.Value.MapValue, propertyMap)
+	errs := buildPropertyMap(namePrefix, m.Properties, propertyMap)
 	if len(errs) > 0 {
 		return errs
 	}
diff --git a/unpack_test.go b/unpack_test.go
index b33ae79..7b314dd 100644
--- a/unpack_test.go
+++ b/unpack_test.go
@@ -27,7 +27,7 @@
 
 var validUnpackTestCases = []struct {
 	input  string
-	output interface{}
+	output []interface{}
 	errs   []error
 }{
 	{`
@@ -36,14 +36,16 @@
 			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 @@
 			name: "abc",
 		}
 		`,
-		struct {
-			Name string
-		}{
-			Name: "abc",
+		[]interface{}{
+			struct {
+				Name string
+			}{
+				Name: "abc",
+			},
 		},
 		nil,
 	},
@@ -66,10 +70,12 @@
 			isGood: true,
 		}
 		`,
-		struct {
-			IsGood bool
-		}{
-			IsGood: true,
+		[]interface{}{
+			struct {
+				IsGood bool
+			}{
+				IsGood: true,
+			},
 		},
 		nil,
 	},
@@ -80,14 +86,16 @@
 			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 @@
 			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 @@
 			}
 		}
 		`,
-		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 @@
 			}
 		}
 		`,
-		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,23 +170,81 @@
 			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"},
 			},
-			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"},
+			},
 		},
 		[]error{
 			&Error{
 				Err: fmt.Errorf("filtered field nested.foo cannot be set in a Blueprint file"),
-				Pos: scanner.Position{"", 27, 4, 8},
+				Pos: mkpos(27, 4, 8),
 			},
 		},
 	},
@@ -238,20 +258,22 @@
 			},
 		}
 		`,
-		struct {
-			EmbeddedStruct
-			Nested struct {
+		[]interface{}{
+			struct {
 				EmbeddedStruct
-			}
-		}{
-			EmbeddedStruct: EmbeddedStruct{
-				Name: "abc",
-			},
-			Nested: struct {
-				EmbeddedStruct
+				Nested struct {
+					EmbeddedStruct
+				}
 			}{
 				EmbeddedStruct: EmbeddedStruct{
-					Name: "def",
+					Name: "abc",
+				},
+				Nested: struct {
+					EmbeddedStruct
+				}{
+					EmbeddedStruct: EmbeddedStruct{
+						Name: "def",
+					},
 				},
 			},
 		},
@@ -267,20 +289,22 @@
 			},
 		}
 		`,
-		struct {
-			EmbeddedInterface
-			Nested struct {
+		[]interface{}{
+			struct {
 				EmbeddedInterface
-			}
-		}{
-			EmbeddedInterface: &struct{ Name string }{
-				Name: "abc",
-			},
-			Nested: 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 @@
 			},
 		}
 		`,
-		struct {
-			Name string
-			EmbeddedStruct
-			Nested struct {
+		[]interface{}{
+			struct {
 				Name string
 				EmbeddedStruct
-			}
-		}{
-			Name: "abc",
-			EmbeddedStruct: EmbeddedStruct{
-				Name: "abc",
-			},
-			Nested: 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 @@
 			},
 		}
 		`,
-		struct {
-			Name string
-			EmbeddedInterface
-			Nested struct {
+		[]interface{}{
+			struct {
 				Name string
 				EmbeddedInterface
-			}
-		}{
-			Name: "abc",
-			EmbeddedInterface: &struct{ Name string }{
-				Name: "abc",
-			},
-			Nested: 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 @@
 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,53 @@
 			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)
+				}
+			}
 		}
 	}
 }
+
+func mkpos(offset, line, column int) scanner.Position {
+	return scanner.Position{
+		Offset: offset,
+		Line:   line,
+		Column: column,
+	}
+}