Skip to content

Commit

Permalink
Progress in decoding values.
Browse files Browse the repository at this point in the history
  • Loading branch information
niemeyer committed Jan 5, 2011
1 parent d00346f commit 41168bb
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 11 deletions.
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ TARG=goyaml

GOFILES=\
goyaml.go\
resolve.go\

CGOFILES=\
parser.go\
decode.go\

CGO_LDFLAGS+=-lm -lpthread
CGO_CFLAGS+=-I$(YAML)/include
CGO_OFILES+=_lib/*.o


Expand All @@ -28,4 +30,4 @@ CLEANFILES=_lib

include $(GOROOT)/src/Make.pkg

#_cgo_defun.c: helpers.c
_cgo_defun.c: helpers.c
172 changes: 172 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package goyaml

/* #include "helpers.c" */
import "C"

import (
"unsafe"
"reflect"
"strconv"
)


type decoder struct {
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{}
d.event = &C.yaml_event_t{}
d.parser = &C.yaml_parser_t{}
C.yaml_parser_initialize(d.parser)

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

d.next()
if d.event._type != C.YAML_STREAM_START_EVENT {
panic("Expected stream start event, got " +
strconv.Itoa(int(d.event._type)))
}
d.next()
return &d
}

func (d *decoder) destroy() {
if d.event._type != C.YAML_NO_EVENT {
C.yaml_event_delete(d.event)
}
C.yaml_parser_delete(d.parser)
}

func (d *decoder) next() {
if d.event._type != C.YAML_NO_EVENT {
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)
}
if C.yaml_parser_parse(d.parser, d.event) == 0 {
panic("Parsing failed.") // XXX Need better error handling here.
}
}

func (d *decoder) skip(_type C.yaml_event_type_t) {
for d.event._type != _type {
d.next()
}
d.next()
}

func (d *decoder) unmarshal(out reflect.Value) bool {
switch d.event._type {
case C.YAML_SCALAR_EVENT:
return d.scalar(out)
case C.YAML_MAPPING_START_EVENT:
return d.mapping(out)
case C.YAML_SEQUENCE_START_EVENT:
return d.sequence(out)
case C.YAML_DOCUMENT_START_EVENT:
return d.document(out)
default:
panic("Attempted to unmarshal unexpected event: " +
strconv.Itoa(int(d.event._type)))
}
return true
}

func (d *decoder) document(out reflect.Value) bool {
d.next()
result := d.unmarshal(out)
if d.event._type != C.YAML_DOCUMENT_END_EVENT {
panic("Expected end of document event but got " +
strconv.Itoa(int(d.event._type)))
}
d.next()
return result
}

func (d *decoder) scalar(out reflect.Value) (ok bool) {
scalar := C.event_scalar(d.event)
str := GoYString(scalar.value)
resolved, _ := resolve(str)
switch out := out.(type) {
case *reflect.StringValue:
out.Set(str)
ok = true
case *reflect.InterfaceValue:
out.Set(reflect.NewValue(resolved))
ok = true
case *reflect.IntValue:
switch resolved := resolved.(type) {
case int:
out.Set(int64(resolved))
ok = true
case int64:
out.Set(resolved)
// ok = true // XXX TEST ME
}
default:
panic("Can't handle scalar type yet: " + out.Type().String())
}
d.next()
return ok
}

func (d *decoder) sequence(out reflect.Value) bool {
sv, ok := out.(*reflect.SliceValue)
if !ok {
d.skip(C.YAML_SEQUENCE_END_EVENT)
return false
}
st := sv.Type().(*reflect.SliceType)
et := st.Elem()

d.next()
for d.event._type != C.YAML_SEQUENCE_END_EVENT {
e := reflect.MakeZero(et)
if ok := d.unmarshal(e); ok {
sv.SetValue(reflect.Append(sv, e))
}
}
d.next()
return true
}

func (d *decoder) mapping(out reflect.Value) bool {
//if iface, ok := out.(*reflect.InterfaceValue); ok {

// XXX What if it's an interface{}?
mv, ok := out.(*reflect.MapValue)
if !ok {
d.skip(C.YAML_MAPPING_END_EVENT)
return false
}
mt := mv.Type().(*reflect.MapType)
kt := mt.Key()
et := mt.Elem()

d.next()
for d.event._type != C.YAML_MAPPING_END_EVENT {
k := reflect.MakeZero(kt)
kok := d.unmarshal(k)
e := reflect.MakeZero(et)
eok := d.unmarshal(e)
if kok && eok {
mv.SetElem(k, e)
}
}
d.next()
return false
}

func GoYString(s *C.yaml_char_t) string {
return C.GoString((*C.char)(unsafe.Pointer(s)))
}
4 changes: 4 additions & 0 deletions goyaml.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package goyaml

import (
"reflect"
"os"
)


func Unmarshal(in []byte, out interface{}) os.Error {
d := newDecoder(in)
defer d.destroy()
d.unmarshal(reflect.NewValue(out))
return nil
}
75 changes: 70 additions & 5 deletions goyaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,82 @@ import (
. "gocheck"
"testing"
"goyaml"
"reflect"
"math"
)

func Test(t *testing.T) { TestingT(t) }

type S struct{}

var _ = Suite(&S{})

type testItem struct {
data string
value interface{}
}


var twoWayTests = []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"}},

// 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: 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}},

// Floats from spec
{"canonical: 6.8523e+5", map[string]interface{}{"canonical": 6.8523e+5}},
{"expo: 685.230_15e+03", map[string]interface{}{"expo": 685.23015e+03}},
{"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}},

// 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}},

// Ints from spec
{"canonical: 685230", map[string]interface{}{"canonical": 685230}},
{"decimal: +685_230", map[string]interface{}{"decimal": 685230}},
{"octal: 02472256", map[string]interface{}{"octal": 685230}},
{"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

// Sequence
{"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}}},
}


func (s *S) TestHelloWorld(c *C) {
data := []byte("hello: world")
value := map[string]string{}
err := goyaml.Unmarshal(data, value)
c.Assert(err, IsNil)
c.Assert(value["hello"], Equals, "world")
for _, item := range twoWayTests {
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()
}
err := goyaml.Unmarshal([]byte(item.data), value)
c.Assert(err, IsNil)
c.Assert(value, Equals, item.value)
}
}
8 changes: 8 additions & 0 deletions helpers.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include <yaml.h>


__typeof__(((yaml_event_t *)0)->data.scalar) * // Sadness.
event_scalar(yaml_event_t *event)
{
return &event->data.scalar;
}
4 changes: 0 additions & 4 deletions parser.go

This file was deleted.

Loading

0 comments on commit 41168bb

Please sign in to comment.