Decoding of structs working.
diff --git a/goyaml_test.go b/all_test.go similarity index 69% rename from goyaml_test.go rename to all_test.go index fcebeee..c7aca37 100644 --- a/goyaml_test.go +++ b/all_test.go
@@ -21,7 +21,7 @@ } -var twoWayTests = []testItem{ +var unmarshalTests = []testItem{ // It will encode either value as a string if asked for. {"hello: world", map[string]string{"hello": "world"}}, {"hello: true", map[string]string{"hello": "true"}}, @@ -33,6 +33,7 @@ {"hello: 0b10", map[string]interface{}{"hello": 2}}, {"hello: 0xA", map[string]interface{}{"hello": 10}}, {"hello: 4294967296", map[string]interface{}{"hello": int64(4294967296)}}, + {"hello: 4294967296", map[string]int64{"hello": int64(4294967296)}}, {"hello: 0.1", map[string]interface{}{"hello": 0.1}}, {"hello: .1", map[string]interface{}{"hello": 0.1}}, {"hello: .Inf", map[string]interface{}{"hello": math.Inf(+1)}}, @@ -47,12 +48,14 @@ //{"sexa: 190:20:30.15", map[string]interface{}{"sexa": 0}}, // Unsupported {"neginf: -.inf", map[string]interface{}{"neginf": math.Inf(-1)}}, {"notanum: .NaN", map[string]interface{}{"notanum": math.NaN}}, + {"fixed: 685_230.15", map[string]float{"fixed": 685230.15}}, // Bools from spec {"canonical: y", map[string]interface{}{"canonical": true}}, {"answer: NO", map[string]interface{}{"answer": false}}, {"logical: True", map[string]interface{}{"logical": true}}, {"option: on", map[string]interface{}{"option": true}}, + {"option: on", map[string]bool{"option": true}}, // Ints from spec {"canonical: 685230", map[string]interface{}{"canonical": 685230}}, @@ -61,23 +64,42 @@ {"hexa: 0x_0A_74_AE", map[string]interface{}{"hexa": 685230}}, {"bin: 0b1010_0111_0100_1010_1110", map[string]interface{}{"bin": 685230}}, //{"sexa: 190:20:30", map[string]interface{}{"sexa": 0}}, // Unsupported + {"decimal: +685_230", map[string]int{"decimal": 685230}}, // Sequence + {"seq: [A,B]", map[string]interface{}{"seq": []interface{}{"A", "B"}}}, {"seq: [A,B,C]", map[string][]string{"seq": []string{"A", "B", "C"}}}, {"seq: [A,1,C]", map[string][]string{"seq": []string{"A", "1", "C"}}}, {"seq: [A,1,C]", map[string][]int{"seq": []int{1}}}, + {"seq: [A,1,C]", map[string]interface{}{"seq": []interface{}{"A", 1, "C"}}}, + + // Map inside interface with no type hints. + {"a: {b: c}", + map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}}, + + // Structs and type conversions. + {"hello: world", &struct{Hello string}{"world"}}, + {"a: {b: c}", &struct{A struct{B string}}{struct{B string}{"c"}}}, + {"a: {b: c}", &struct{A *struct{B string}}{&struct{B string}{"c"}}}, + {"a: 1", &struct{A int}{1}}, + {"a: [1, 2]", &struct{A []int}{[]int{1, 2}}}, + {"a: 1", &struct{B int}{0}}, + {"a: 1", &struct{B int "a"}{1}}, + {"a: y", &struct{A bool}{true}}, } -func (s *S) TestHelloWorld(c *C) { - for _, item := range twoWayTests { +func (s *S) TestUnmarshal(c *C) { + for _, item := range unmarshalTests { t := reflect.NewValue(item.value).Type() var value interface{} if t, ok := t.(*reflect.MapType); ok { value = reflect.MakeMap(t).Interface() } else { - zero := reflect.MakeZero(reflect.NewValue(item.value).Type()) - value = zero.Interface() + pt := reflect.NewValue(item.value).Type().(*reflect.PtrType) + pv := reflect.MakeZero(pt).(*reflect.PtrValue) + pv.PointTo(reflect.MakeZero(pt.Elem())) + value = pv.Interface() } err := goyaml.Unmarshal([]byte(item.data), value) c.Assert(err, IsNil)
diff --git a/decode.go b/decode.go index d2042d6..de31cbf 100644 --- a/decode.go +++ b/decode.go
@@ -65,6 +65,12 @@ d.next() } +var blackHole = reflect.NewValue(true) + +func (d *decoder) drop() { + d.unmarshal(blackHole) +} + func (d *decoder) unmarshal(out reflect.Value) bool { switch d.event._type { case C.YAML_SCALAR_EVENT: @@ -111,7 +117,19 @@ ok = true case int64: out.Set(resolved) - // ok = true // XXX TEST ME + ok = true + } + case *reflect.BoolValue: + switch resolved := resolved.(type) { + case bool: + out.Set(resolved) + ok = true + } + case *reflect.FloatValue: + switch resolved := resolved.(type) { + case float: + out.Set(float64(resolved)) + ok = true } default: panic("Can't handle scalar type yet: " + out.Type().String()) @@ -121,6 +139,12 @@ } func (d *decoder) sequence(out reflect.Value) bool { + if iface, ok := out.(*reflect.InterfaceValue); ok { + // No type hints. Will have to use a generic sequence. + out = reflect.NewValue(make([]interface{}, 0)) + iface.SetValue(out) + } + sv, ok := out.(*reflect.SliceValue) if !ok { d.skip(C.YAML_SEQUENCE_END_EVENT) @@ -140,10 +164,36 @@ return true } -func (d *decoder) mapping(out reflect.Value) bool { - //if iface, ok := out.(*reflect.InterfaceValue); ok { +func indirect(out reflect.Value) reflect.Value { + for { + switch v := out.(type) { + case *reflect.PtrValue: + if v.IsNil() { + out = reflect.MakeZero(v.Type().(*reflect.PtrType).Elem()) + v.PointTo(out) + } else { + out = v.Elem() + } + default: + return out + } + } + panic("Unreachable") +} - // XXX What if it's an interface{}? +func (d *decoder) mapping(out reflect.Value) bool { + out = indirect(out) + + if s, ok := out.(*reflect.StructValue); ok { + return d.mappingStruct(s) + } + + if iface, ok := out.(*reflect.InterfaceValue); ok { + // No type hints. Will have to use a generic map. + out = reflect.NewValue(make(map[interface{}]interface{})) + iface.SetValue(out) + } + mv, ok := out.(*reflect.MapValue) if !ok { d.skip(C.YAML_MAPPING_END_EVENT) @@ -164,7 +214,29 @@ } } d.next() - return false + return true +} + +func (d *decoder) mappingStruct(out *reflect.StructValue) bool { + fields, err := getStructFields(out.Type().(*reflect.StructType)) + if err != nil { + panic(err) + } + name := reflect.NewValue("").(*reflect.StringValue) + fieldsMap := fields.Map + d.next() + for d.event._type != C.YAML_MAPPING_END_EVENT { + if d.unmarshal(name) { + if info, ok := fieldsMap[name.Get()]; ok { + d.unmarshal(out.Field(info.Num)) + continue + } + } + // Can't unmarshal name, or it's not present in struct. + d.drop() + } + d.next() + return true } func GoYString(s *C.yaml_char_t) string {
diff --git a/goyaml.go b/goyaml.go index b3bcecf..aa43a4a 100644 --- a/goyaml.go +++ b/goyaml.go
@@ -2,6 +2,8 @@ import ( "reflect" + "strings" + "sync" "os" ) @@ -12,3 +14,104 @@ d.unmarshal(reflect.NewValue(out)) return nil } + + +// -------------------------------------------------------------------------- +// Maintain a mapping of keys to structure field indexes + +// The code in this section was copied from gobson. + +type structFields struct { + Map map[string]fieldInfo + List []fieldInfo +} + +type fieldInfo struct { + Key string + Num int + Conditional bool +} + +var fieldMap = make(map[string]*structFields) +var fieldMapMutex sync.RWMutex + +func getStructFields(st *reflect.StructType) (*structFields, os.Error) { + path := st.PkgPath() + name := st.Name() + + fullName := path + "." + name + fieldMapMutex.RLock() + fields, found := fieldMap[fullName] + fieldMapMutex.RUnlock() + if found { + return fields, nil + } + + n := st.NumField() + fieldsMap := make(map[string]fieldInfo) + fieldsList := make([]fieldInfo, n) + for i := 0; i != n; i++ { + field := st.Field(i) + if field.PkgPath != "" { + continue // Private field + } + + info := fieldInfo{Num: i} + + if s := strings.LastIndex(field.Tag, "/"); s != -1 { + for _, c := range field.Tag[s+1:] { + switch c { + case int('c'): + info.Conditional = true + default: + panic("Unsupported field flag: " + string([]int{c})) + } + } + field.Tag = field.Tag[:s] + } + + if field.Tag != "" { + info.Key = field.Tag + } else { + info.Key = strings.ToLower(field.Name) + } + + if _, found = fieldsMap[info.Key]; found { + msg := "Duplicated key '" + info.Key + "' in struct " + st.String() + return nil, os.NewError(msg) + } + + fieldsList[len(fieldsMap)] = info + fieldsMap[info.Key] = info + } + + fields = &structFields{fieldsMap, fieldsList[:len(fieldsMap)]} + + if fullName != "." { + fieldMapMutex.Lock() + fieldMap[fullName] = fields + fieldMapMutex.Unlock() + } + + return fields, nil +} + +func isZero(v reflect.Value) bool { + switch v := v.(type) { + case *reflect.StringValue: + return len(v.Get()) == 0 + case *reflect.InterfaceValue: + return v.IsNil() + case *reflect.SliceValue: + return v.Len() == 0 + case *reflect.MapValue: + return v.Len() == 0 + case *reflect.IntValue: + return v.Get() == 0 + case *reflect.UintValue: + return v.Get() == 0 + case *reflect.BoolValue: + return !v.Get() + } + return false +}