Просмотр исходного кода

Implemented encoding of values.

Gustavo Niemeyer 15 лет назад
Родитель
Сommit
e334f8522a
9 измененных файлов с 523 добавлено и 52 удалено
  1. 11 4
      Makefile
  2. 39 16
      decode.go
  3. 29 21
      decode_test.go
  4. 278 0
      encode.go
  5. 130 0
      encode_test.go
  6. 16 7
      goyaml.go
  7. 8 2
      helpers.c
  8. 10 0
      helpers.h
  9. 2 2
      resolve.go

+ 11 - 4
Makefile

@@ -11,15 +11,24 @@ GOFILES=\
 
 CGOFILES=\
 	decode.go\
+	encode.go\
 
 CGO_LDFLAGS+=-lm -lpthread
 CGO_CFLAGS+=-I$(YAML)/include
-CGO_OFILES+=_lib/*.o
+#CGO_OFILES+=_lib/*.o
+CGO_OFILES+=\
+	helpers.o\
+	_lib/api.o\
+	_lib/scanner.o\
+	_lib/reader.o\
+	_lib/parser.o\
+	_lib/writer.o\
+	_lib/emitter.o\
 
 
 all: package
 
-$(CGO_OFILES): $(LIBYAML)
+_lib/api.o: $(LIBYAML)
 	@mkdir -p _lib
 	cd _lib && ar x $(LIBYAML)
 
@@ -29,5 +38,3 @@ $(LIBYAML):
 CLEANFILES=_lib
 
 include $(GOROOT)/src/Make.pkg
-
-_cgo_defun.c: helpers.c

+ 39 - 16
decode.go

@@ -1,6 +1,6 @@
 package goyaml
 
-/* #include "helpers.c" */
+// #include "helpers.h"
 import "C"
 
 import (
@@ -11,24 +11,24 @@ import (
 
 
 type decoder struct {
-    parser *C.yaml_parser_t
-    event *C.yaml_event_t
+    parser C.yaml_parser_t
+    event C.yaml_event_t
 }
 
 func newDecoder(b []byte) *decoder {
-    if len(b) == 0 {
-        panic("Can't handle empty buffers yet") // XXX Fix this.
+    d := decoder{}
+    if C.yaml_parser_initialize(&d.parser) == 0 {
+        panic("Failed to initialize YAML emitter")
     }
 
-    d := decoder{}
-    d.event = &C.yaml_event_t{}
-    d.parser = &C.yaml_parser_t{}
-    C.yaml_parser_initialize(d.parser)
+    if len(b) == 0 {
+        b = []byte{'\n'}
+    }
 
     // How unsafe is this really?  Will this break if the GC becomes compacting?
     // Probably not, otherwise that would likely break &parse below as well.
     input := (*C.uchar)(unsafe.Pointer(&b[0]))
-    C.yaml_parser_set_input_string(d.parser, input, (C.size_t)(len(b)))
+    C.yaml_parser_set_input_string(&d.parser, input, (C.size_t)(len(b)))
 
     d.next()
     if d.event._type != C.YAML_STREAM_START_EVENT {
@@ -41,9 +41,9 @@ func newDecoder(b []byte) *decoder {
 
 func (d *decoder) destroy() {
     if d.event._type != C.YAML_NO_EVENT {
-        C.yaml_event_delete(d.event)
+        C.yaml_event_delete(&d.event)
     }
-    C.yaml_parser_delete(d.parser)
+    C.yaml_parser_delete(&d.parser)
 }
 
 func (d *decoder) next() {
@@ -51,11 +51,32 @@ func (d *decoder) next() {
         if d.event._type == C.YAML_STREAM_END_EVENT {
             panic("Attempted to go past the end of stream. Corrupted value?")
         }
-        C.yaml_event_delete(d.event)
+        C.yaml_event_delete(&d.event)
+    }
+    if C.yaml_parser_parse(&d.parser, &d.event) == 0 {
+        d.fail("")
     }
-    if C.yaml_parser_parse(d.parser, d.event) == 0 {
-        panic("Parsing failed.") // XXX Need better error handling here.
+}
+
+func (d *decoder) fail(msg string) {
+    var where string
+    var line int
+    if d.parser.problem_mark.line != 0 {
+        line = int(C.int(d.parser.problem_mark.line))
+    } else if d.parser.context_mark.line != 0 {
+        line = int(C.int(d.parser.context_mark.line))
+    }
+    if line != 0 {
+        where = "line " + strconv.Itoa(line) + ": "
+    }
+    if msg == "" {
+        if d.parser.problem != nil {
+            msg = C.GoString(d.parser.problem)
+        } else {
+            msg = "Unknown problem parsing YAML content"
+        }
     }
+    panic(where + msg)
 }
 
 func (d *decoder) skip(_type C.yaml_event_type_t) {
@@ -125,6 +146,8 @@ func (d *decoder) unmarshal(out reflect.Value) (good bool) {
         good = d.sequence(out)
     case C.YAML_DOCUMENT_START_EVENT:
         good = d.document(out)
+    case C.YAML_STREAM_END_EVENT:
+        // Happens when attempting to decode an empty buffer.
     default:
         panic("Attempted to unmarshal unexpected event: " +
               strconv.Itoa(int(d.event._type)))
@@ -144,7 +167,7 @@ func (d *decoder) document(out reflect.Value) bool {
 }
 
 func (d *decoder) scalar(out reflect.Value) (good bool) {
-    scalar := C.event_scalar(d.event)
+    scalar := C.event_scalar(&d.event)
     str := GoYString(scalar.value)
     tag := GoYString(scalar.tag)
     var resolved interface{}

+ 29 - 21
decode_test.go

@@ -9,25 +9,30 @@ import (
 )
 
 
+var unmarshalIntTest = 123
+
 var unmarshalTests = []struct{data string; value interface{}}{
-    // 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"}},
-
-    // And when given the option, will preserve the YAML type.
-    {"hello: world", map[string]interface{}{"hello": "world"}},
-    {"hello: true", map[string]interface{}{"hello": true}},
-    {"hello: 10", map[string]interface{}{"hello": 10}},
-    {"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)}},
-    {"hello: -.Inf", map[string]interface{}{"hello": math.Inf(-1)}},
-    {"hello: -10", map[string]interface{}{"hello": -10}},
-    {"hello: -.1", map[string]interface{}{"hello": -0.1}},
+    {"", &struct{}{}},
+    {"{}", &struct{}{}},
+
+    {"v: hi", map[string]string{"v": "hi"}},
+    {"v: hi", map[string]interface{}{"v": "hi"}},
+    {"v: true", map[string]string{"v": "true"}},
+    {"v: true", map[string]interface{}{"v": true}},
+    {"v: 10", map[string]interface{}{"v": 10}},
+    {"v: 0b10", map[string]interface{}{"v": 2}},
+    {"v: 0xA", map[string]interface{}{"v": 10}},
+    {"v: 4294967296", map[string]interface{}{"v": int64(4294967296)}},
+    {"v: 4294967296", map[string]int64{"v": int64(4294967296)}},
+    {"v: 0.1", map[string]interface{}{"v": 0.1}},
+    {"v: .1", map[string]interface{}{"v": 0.1}},
+    {"v: .Inf", map[string]interface{}{"v": math.Inf(+1)}},
+    {"v: -.Inf", map[string]interface{}{"v": math.Inf(-1)}},
+    {"v: -10", map[string]interface{}{"v": -10}},
+    {"v: -.1", map[string]interface{}{"v": -0.1}},
+
+    // Simple values.
+    {"123", &unmarshalIntTest},
 
     // Floats from spec
     {"canonical: 6.8523e+5", map[string]interface{}{"canonical": 6.8523e+5}},
@@ -35,7 +40,7 @@ var unmarshalTests = []struct{data string; value interface{}}{
     {"fixed: 685_230.15", map[string]interface{}{"fixed": 685230.15}},
     //{"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}},
+    {"notanum: .NaN", map[string]interface{}{"notanum": math.NaN()}},
     {"fixed: 685_230.15", map[string]float{"fixed": 685230.15}},
 
     // Bools from spec
@@ -94,7 +99,7 @@ var unmarshalTests = []struct{data string; value interface{}}{
     {"v: 128", map[string]int8{}},
 
     // Quoted values.
-    {"'1': '2'", map[interface{}]interface{}{"1": "2"}},
+    {"'1': '\"2\"'", map[interface{}]interface{}{"1": "\"2\""}},
 
     // Explicit tags.
     {"v: !!float '1.1'", map[string]interface{}{"v": 1.1}},
@@ -123,7 +128,10 @@ func (s *S) TestUnmarshal(c *C) {
 }
 
 var unmarshalErrorTests = []struct{data, error string}{
-    {"v: !!float 'error'", "Can't decode !!str 'error' as a !!float"},
+    {"v: !!float 'error'",
+     "YAML error: Can't decode !!str 'error' as a !!float"},
+    {"v: [A,", "YAML error: line 1: did not find expected node content"},
+    {"v:\n- [A,", "YAML error: line 2: did not find expected node content"},
 }
 
 func (s *S) TestUnmarshalErrors(c *C) {

+ 278 - 0
encode.go

@@ -0,0 +1,278 @@
+package goyaml
+
+// #include "helpers.h"
+import "C"
+
+import (
+    "reflect"
+    "unsafe"
+    "strconv"
+)
+
+
+type encoder struct {
+    emitter C.yaml_emitter_t
+    event C.yaml_event_t
+    out []byte
+    tmp []byte
+    tmph *reflect.SliceHeader
+}
+
+
+//export outputHandler
+func outputHandler(data unsafe.Pointer, buffer *C.uchar, size C.size_t) C.int {
+    e := (*encoder)(data)
+    e.tmph.Data = uintptr(unsafe.Pointer(buffer))
+    e.tmph.Len = int(size)
+    e.tmph.Cap = int(size)
+    e.out = append(e.out, e.tmp...)
+    return 1
+}
+
+func newEncoder() (e *encoder) {
+    e = &encoder{}
+    e.tmph = (*reflect.SliceHeader)(unsafe.Pointer(&e.tmp))
+    if C.yaml_emitter_initialize(&e.emitter) == 0 {
+        panic("Failed to initialize YAML emitter")
+    }
+    C.set_output_handler(&e.emitter)
+    C.yaml_stream_start_event_initialize(&e.event, C.YAML_UTF8_ENCODING)
+    e.emit()
+    C.yaml_document_start_event_initialize(&e.event, nil, nil, nil, 1)
+    e.emit()
+    return e
+}
+
+func (e *encoder) finish() {
+    C.yaml_document_end_event_initialize(&e.event, 1)
+    e.emit()
+    e.emitter.open_ended = 0
+    C.yaml_stream_end_event_initialize(&e.event)
+    e.emit()
+}
+
+func (e *encoder) destroy() {
+    C.yaml_emitter_delete(&e.emitter)
+}
+
+func (e *encoder) emit() {
+    // This will internally delete the e.event value.
+    if C.yaml_emitter_emit(&e.emitter, &e.event) == 0 &&
+       e.event._type != C.YAML_DOCUMENT_END_EVENT &&
+       e.event._type != C.YAML_STREAM_END_EVENT {
+        if e.emitter.error == C.YAML_EMITTER_ERROR {
+            // XXX TESTME
+            panic("YAML emitter error: " + C.GoString(e.emitter.problem))
+        } else {
+            // XXX TESTME
+            panic("Unknown YAML emitter error")
+        }
+    }
+}
+
+func (e *encoder) fail(msg string) {
+    if msg == "" {
+        if e.emitter.problem != nil {
+            msg = C.GoString(e.emitter.problem)
+        } else {
+            msg = "Unknown problem generating YAML content"
+        }
+    }
+    panic(msg)
+}
+
+func (e *encoder) marshal(tag string, in reflect.Value) {
+    var value interface{}
+    if getter, ok := in.Interface().(Getter); ok {
+        tag, value = getter.GetYAML()
+        if value == nil {
+            e.nilv()
+            return
+        }
+        in = reflect.NewValue(value)
+    }
+    switch in := in.(type) {
+    case *reflect.InterfaceValue:
+        if in.IsNil() {
+            e.nilv()
+        } else {
+            e.marshal(tag, in.Elem())
+        }
+    case *reflect.MapValue:
+        e.mapv(tag, in)
+    case *reflect.PtrValue:
+        if in.IsNil() {
+            e.nilv()
+        } else {
+            e.marshal(tag, in.Elem())
+        }
+    case *reflect.StructValue:
+        e.structv(tag, in)
+    case *reflect.SliceValue:
+        e.slicev(tag, in)
+    case *reflect.StringValue:
+        e.stringv(tag, in)
+    case *reflect.IntValue:
+        e.intv(tag, in)
+    case *reflect.UintValue:
+        e.uintv(tag, in)
+    case *reflect.FloatValue:
+        e.floatv(tag, in)
+    case *reflect.BoolValue:
+        e.boolv(tag, in)
+    default:
+        panic("Can't marshal type yet: " + in.Type().String())
+    }
+}
+
+func (e *encoder) mapv(tag string, in *reflect.MapValue) {
+    var ctag *C.yaml_char_t
+    var free func()
+    var cimplicit C.int
+    if tag != "" {
+        ctag, free = ystr(tag)
+        defer free()
+        cimplicit = 0
+    } else {
+        cimplicit = 1
+    }
+    C.yaml_mapping_start_event_initialize(&e.event, nil, ctag, cimplicit,
+                                          C.YAML_BLOCK_MAPPING_STYLE)
+    e.emit()
+    for _, k := range in.Keys() {
+        e.marshal("", k)
+        e.marshal("", in.Elem(k))
+    }
+    C.yaml_mapping_end_event_initialize(&e.event)
+    e.emit()
+}
+
+func (e *encoder) structv(tag string, in *reflect.StructValue) {
+    var ctag *C.yaml_char_t
+    var free func()
+    cimplicit := C.int(1)
+    if tag != "" {
+        ctag, free = ystr(tag)
+        defer free()
+        cimplicit = 0
+    }
+    C.yaml_mapping_start_event_initialize(&e.event, nil, ctag, cimplicit,
+                                          C.YAML_BLOCK_MAPPING_STYLE)
+    e.emit()
+    fields, err := getStructFields(in.Type().(*reflect.StructType))
+    if err != nil {
+        panic(err)
+    }
+    for i, info := range fields.List {
+        value := in.Field(i)
+        if info.Conditional && isZero(value) {
+            continue
+        }
+        e.marshal("", reflect.NewValue(info.Key))
+        e.marshal("", value)
+    }
+    C.yaml_mapping_end_event_initialize(&e.event)
+    e.emit()
+}
+
+func (e *encoder) slicev(tag string, in *reflect.SliceValue) {
+    var ctag *C.yaml_char_t
+    var free func()
+    var cimplicit C.int
+    if tag != "" {
+        ctag, free = ystr(tag)
+        defer free()
+        cimplicit = 0
+    } else {
+        cimplicit = 1
+    }
+    C.yaml_sequence_start_event_initialize(&e.event, nil, ctag, cimplicit,
+                                           C.YAML_BLOCK_SEQUENCE_STYLE)
+    e.emit()
+    n := in.Len()
+    for i := 0; i < n; i++ {
+        e.marshal("", in.Elem(i))
+    }
+    C.yaml_sequence_end_event_initialize(&e.event)
+    e.emit()
+}
+
+func (e *encoder) stringv(tag string, in *reflect.StringValue) {
+    var style C.yaml_scalar_style_t
+    s := in.Get()
+    if rtag, _ := resolve("", s); rtag != "!!str" {
+        style = C.YAML_DOUBLE_QUOTED_SCALAR_STYLE
+    } else {
+        style = C.YAML_PLAIN_SCALAR_STYLE
+    }
+    e.emitScalar(s, "", tag, style)
+}
+
+func (e *encoder) boolv(tag string, in *reflect.BoolValue) {
+    var s string
+    if in.Get() {
+        s = "true"
+    } else {
+        s = "false"
+    }
+    e.emitScalar(s, "", tag, C.YAML_PLAIN_SCALAR_STYLE)
+}
+
+func (e *encoder) intv(tag string, in *reflect.IntValue) {
+    s := strconv.Itoa64(in.Get())
+    e.emitScalar(s, "", tag, C.YAML_PLAIN_SCALAR_STYLE)
+}
+
+func (e *encoder) uintv(tag string, in *reflect.UintValue) {
+    s := strconv.Uitoa64(in.Get())
+    e.emitScalar(s, "", tag, C.YAML_PLAIN_SCALAR_STYLE)
+}
+
+func (e *encoder) floatv(tag string, in *reflect.FloatValue) {
+    // FIXME: Handle 64 bits here.
+    s := strconv.Ftoa32(float32(in.Get()), 'g', -1)
+    switch s {
+    case "+Inf": s = ".inf"
+    case "-Inf": s = "-.inf"
+    case "NaN": s = ".nan"
+    }
+    e.emitScalar(s, "", tag, C.YAML_PLAIN_SCALAR_STYLE)
+}
+
+func (e *encoder) nilv() {
+    e.emitScalar("null", "", "", C.YAML_PLAIN_SCALAR_STYLE)
+}
+
+func (e *encoder) emitScalar(value, anchor, tag string,
+                             style C.yaml_scalar_style_t) {
+    var canchor, ctag, cvalue *C.yaml_char_t
+    var cimplicit C.int
+    var free func()
+    if anchor != "" {
+        canchor, free = ystr(anchor)
+        defer free()
+    }
+    if tag != "" {
+        ctag, free = ystr(tag)
+        defer free()
+        cimplicit = 0
+        style = C.YAML_PLAIN_SCALAR_STYLE
+    } else {
+        cimplicit = 1
+    }
+    cvalue, free = ystr(value)
+    defer free()
+    size := C.int(len(value))
+    if C.yaml_scalar_event_initialize(&e.event, canchor, ctag, cvalue, size,
+                                      cimplicit, cimplicit, style) == 0 {
+        e.fail("")
+    }
+    e.emit()
+}
+
+func ystr(s string) (ys *C.yaml_char_t, free func()) {
+    up := unsafe.Pointer(C.CString(s))
+    ys = (*C.yaml_char_t)(up)
+    free = func() { C.free(up) }
+    return ys, free
+}

+ 130 - 0
encode_test.go

@@ -0,0 +1,130 @@
+package goyaml_test
+
+
+import (
+    . "gocheck"
+    "goyaml"
+    "math"
+    //"reflect"
+)
+
+var marshalIntTest = 123
+
+var marshalTests = []struct{data string; value interface{}}{
+    {"{}\n", &struct{}{}},
+    {"v: hi\n", map[string]string{"v": "hi"}},
+    {"v: hi\n", map[string]interface{}{"v": "hi"}},
+    {"v: \"true\"\n", map[string]string{"v": "true"}},
+    {"v: \"false\"\n", map[string]string{"v": "false"}},
+    {"v: true\n", map[string]interface{}{"v": true}},
+    {"v: false\n", map[string]interface{}{"v": false}},
+    {"v: 10\n", map[string]interface{}{"v": 10}},
+    {"v: -10\n", map[string]interface{}{"v": -10}},
+    {"v: 42\n", map[string]uint{"v": 42}},
+    {"v: 4294967296\n", map[string]interface{}{"v": int64(4294967296)}},
+    {"v: 4294967296\n", map[string]int64{"v": int64(4294967296)}},
+    {"v: 4294967296\n", map[string]uint64{"v": 4294967296}},
+    {"v: \"10\"\n", map[string]interface{}{"v": "10"}},
+    {"v: 0.1\n", map[string]interface{}{"v": 0.1}},
+    {"v: 0.1\n", map[string]interface{}{"v": float64(0.1)}},
+    {"v: -0.1\n", map[string]interface{}{"v": -0.1}},
+    {"v: .inf\n", map[string]interface{}{"v": math.Inf(+1)}},
+    {"v: -.inf\n", map[string]interface{}{"v": math.Inf(-1)}},
+    {"v: .nan\n", map[string]interface{}{"v": math.NaN()}},
+    {"v: null\n", map[string]interface{}{"v": nil}},
+    {"v: \"\"\n", map[string]interface{}{"v": ""}},
+    {"v:\n- A\n- B\n", map[string][]string{"v": []string{"A", "B"}}},
+    {"v:\n- A\n- 1\n", map[string][]interface{}{"v": []interface{}{"A", 1}}},
+    {"a:\n  b: c\n",
+     map[string]interface{}{"a": map[interface{}]interface{}{"b": "c"}}},
+
+    // Simple values.
+    {"123\n", &marshalIntTest},
+
+    // Structures
+    {"hello: world\n", &struct{Hello string}{"world"}},
+    {"a:\n  b: c\n", &struct{A struct{B string}}{struct{B string}{"c"}}},
+    {"a:\n  b: c\n", &struct{A *struct{B string}}{&struct{B string}{"c"}}},
+    {"a: null\n", &struct{A *struct{B string}}{}},
+    {"a: 1\n", &struct{A int}{1}},
+    {"a:\n- 1\n- 2\n", &struct{A []int}{[]int{1, 2}}},
+    {"a: 1\n", &struct{B int "a"}{1}},
+    {"a: true\n", &struct{A bool}{true}},
+
+    // Conditional flag
+    {"a: 1\n", &struct{A int "a/c"; B int "b/c"}{1, 0}},
+    {"{}\n", &struct{A int "a/c"; B int "b/c"}{0, 0}},
+}
+
+
+func (s *S) TestMarshal(c *C) {
+    for _, item := range marshalTests {
+        data, err := goyaml.Marshal(item.value)
+        c.Assert(err, IsNil)
+        c.Assert(string(data), Equals, item.data)
+    }
+}
+
+//var unmarshalErrorTests = []struct{data, error string}{
+//    {"v: !!float 'error'", "Can't decode !!str 'error' as a !!float"},
+//}
+//
+//func (s *S) TestUnmarshalErrors(c *C) {
+//    for _, item := range unmarshalErrorTests {
+//        var value interface{}
+//        err := goyaml.Unmarshal([]byte(item.data), &value)
+//        c.Assert(err, Matches, item.error)
+//    }
+//}
+
+var marshalTaggedIfaceTest interface{} = &struct{A string}{"B"}
+
+var getterTests = []struct{data, tag string; value interface{}}{
+    {"_:\n  hi: there\n", "", map[interface{}]interface{}{"hi": "there"}},
+    {"_:\n- 1\n- A\n", "", []interface{}{1, "A"}},
+    {"_: 10\n", "", 10},
+    {"_: null\n", "", nil},
+    {"_: !foo BAR!\n", "!foo", "BAR!"},
+    {"_: !foo 1\n", "!foo", "1"},
+    {"_: !foo '\"1\"'\n", "!foo", "\"1\""},
+    {"_: !foo 1.1\n", "!foo", 1.1},
+    {"_: !foo 1\n", "!foo", 1},
+    {"_: !foo 1\n", "!foo", uint(1)},
+    {"_: !foo true\n", "!foo", true},
+    {"_: !foo\n- A\n- B\n", "!foo", []string{"A", "B"}},
+    {"_: !foo\n  A: B\n", "!foo", map[string]string{"A": "B"}},
+    {"_: !foo\n  a: B\n", "!foo", &marshalTaggedIfaceTest},
+}
+
+type typeWithGetter struct {
+    tag string
+    value interface{}
+}
+
+func (o typeWithGetter) GetYAML() (tag string, value interface{}) {
+    return o.tag, o.value
+}
+
+type typeWithGetterField struct {
+    Field typeWithGetter "_"
+}
+
+func (s *S) TestMashalWithGetter(c *C) {
+    for _, item := range getterTests {
+        obj := &typeWithGetterField{}
+        obj.Field.tag = item.tag
+        obj.Field.value = item.value
+        data, err := goyaml.Marshal(obj)
+        c.Assert(err, IsNil)
+        c.Assert(string(data), Equals, string(item.data))
+    }
+}
+
+func (s *S) TestUnmarshalWholeDocumentWithGetter(c *C) {
+    obj := &typeWithGetter{}
+    obj.tag = ""
+    obj.value = map[string]string{"hello": "world!"}
+    data, err := goyaml.Marshal(obj)
+    c.Assert(err, IsNil)
+    c.Assert(string(data), Equals, "hello: world!\n")
+}

+ 16 - 7
goyaml.go

@@ -13,7 +13,7 @@ func handleErr(err *os.Error) {
         if _, ok := r.(runtime.Error); ok {
             panic(r)
         } else if s, ok := r.(string); ok {
-            *err = os.ErrorString(s)
+            *err = os.ErrorString("YAML error: " + s)
         } else if e, ok := r.(os.Error); ok {
             *err = e
         } else {
@@ -22,6 +22,13 @@ func handleErr(err *os.Error) {
     }
 }
 
+type Setter interface {
+    SetYAML(tag string, value interface{}) bool
+}
+
+type Getter interface {
+    GetYAML() (tag string, value interface{})
+}
 
 func Unmarshal(in []byte, out interface{}) (err os.Error) {
     defer handleErr(&err)
@@ -31,12 +38,14 @@ func Unmarshal(in []byte, out interface{}) (err os.Error) {
     return nil
 }
 
-type Setter interface {
-    SetYAML(tag string, value interface{}) bool
-}
-
-type Getter interface {
-    GetYAML() (tag string, value interface{})
+func Marshal(in interface{}) (out []byte, err os.Error) {
+    defer handleErr(&err)
+    e := newEncoder()
+    defer e.destroy()
+    e.marshal("", reflect.NewValue(in))
+    e.finish()
+    out = e.out
+    return
 }
 
 

+ 8 - 2
helpers.c

@@ -1,8 +1,14 @@
-#include <yaml.h>
+#include "_cgo_export.h"
+#include "helpers.h"
 
 
 __typeof__(((yaml_event_t *)0)->data.scalar) * // Sadness.
 event_scalar(yaml_event_t *event)
 {
-	return &event->data.scalar;
+    return &event->data.scalar;
+}
+
+void set_output_handler(yaml_emitter_t *e)
+{
+    yaml_emitter_set_output(e, outputHandler, (void *)e);
 }

+ 10 - 0
helpers.h

@@ -0,0 +1,10 @@
+#ifndef helpers_h
+#define helpers_h
+
+#include <yaml.h>
+
+__typeof__(((yaml_event_t *)0)->data.scalar) *event_scalar(yaml_event_t *event);
+
+void set_output_handler(yaml_emitter_t *e);
+
+#endif

+ 2 - 2
resolve.go

@@ -7,7 +7,7 @@ import (
 )
 
 
-// TODO: Support merge, timestamps, and base 60 floats.
+// TODO: merge, timestamps, base 60 floats, omap.
 
 
 type resolveMapItem struct {
@@ -40,7 +40,7 @@ func init() {
         {false, "!!bool", []string{"false", "False", "FALSE"}},
         {false, "!!bool", []string{"off", "Off", "OFF"}},
         {nil, "!!null", []string{"~", "null", "Null", "NULL"}},
-        {math.NaN, "!!float", []string{".nan", ".NaN", ".NAN"}},
+        {math.NaN(), "!!float", []string{".nan", ".NaN", ".NAN"}},
         {math.Inf(+1), "!!float", []string{".inf", ".Inf", ".INF"}},
         {math.Inf(+1), "!!float", []string{"+.inf", "+.Inf", "+.INF"}},
         {math.Inf(-1), "!!float", []string{"-.inf", "-.Inf", "-.INF"}},