Merge pull request #21 from mattes/patch-1

Fix trivial documentation typo. 
diff --git a/LICENSE b/LICENSE
index 53320c3..a68e67f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,3 +1,6 @@
+
+Copyright (c) 2011-2014 - Canonical Inc.
+
 This software is licensed under the LGPLv3, included below.
 
 As a special exception to the GNU Lesser General Public License version 3
diff --git a/README.md b/README.md
index 896687b..af07056 100644
--- a/README.md
+++ b/README.md
@@ -12,10 +12,10 @@
 Compatibility
 -------------
 
-The yaml package is almost compatible with YAML 1.1, including support for
-anchors, tags, etc. There are still a few missing bits, such as document
-merging, base-60 floats (huh?), and multi-document unmarshalling. These
-features are not hard to add, and will be introduced as necessary.
+The yaml package supports most of YAML 1.1 and 1.2, including support for
+anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
+implemented, and base-60 floats from YAML 1.1 are purposefully not
+supported since they're a poor design and are gone in YAML 1.2.
 
 Installation and usage
 ----------------------
diff --git a/decode.go b/decode.go
index 74eda3c..a098626 100644
--- a/decode.go
+++ b/decode.go
@@ -1,6 +1,8 @@
 package yaml
 
 import (
+	"encoding/base64"
+	"fmt"
 	"reflect"
 	"strconv"
 	"time"
@@ -63,7 +65,7 @@
 func (p *parser) skip() {
 	if p.event.typ != yaml_NO_EVENT {
 		if p.event.typ == yaml_STREAM_END_EVENT {
-			panic("Attempted to go past the end of stream. Corrupted value?")
+			fail("Attempted to go past the end of stream. Corrupted value?")
 		}
 		yaml_event_delete(&p.event)
 	}
@@ -89,7 +91,7 @@
 	} else {
 		msg = "Unknown problem parsing YAML content"
 	}
-	panic(where + msg)
+	fail(where + msg)
 }
 
 func (p *parser) anchor(n *node, anchor []byte) {
@@ -114,10 +116,9 @@
 		// Happens when attempting to decode an empty buffer.
 		return nil
 	default:
-		panic("Attempted to parse unknown event: " +
-			strconv.Itoa(int(p.event.typ)))
+		panic("Attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ)))
 	}
-	panic("Unreachable")
+	panic("unreachable")
 }
 
 func (p *parser) node(kind int) *node {
@@ -135,8 +136,7 @@
 	p.skip()
 	n.children = append(n.children, p.parse())
 	if p.event.typ != yaml_DOCUMENT_END_EVENT {
-		panic("Expected end of document event but got " +
-			strconv.Itoa(int(p.event.typ)))
+		panic("Expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
 	}
 	p.skip()
 	return n
@@ -218,7 +218,7 @@
 			var arg interface{}
 			*out = reflect.ValueOf(&arg).Elem()
 			return func() {
-				*good = setter.SetYAML(tag, arg)
+				*good = setter.SetYAML(shortTag(tag), arg)
 			}
 		}
 	}
@@ -226,7 +226,7 @@
 	for again {
 		again = false
 		setter, _ := (*out).Interface().(Setter)
-		if tag != "!!null" || setter != nil {
+		if tag != yaml_NULL_TAG || setter != nil {
 			if pv := (*out); pv.Kind() == reflect.Ptr {
 				if pv.IsNil() {
 					*out = reflect.New(pv.Type().Elem()).Elem()
@@ -242,7 +242,7 @@
 			var arg interface{}
 			*out = reflect.ValueOf(&arg).Elem()
 			return func() {
-				*good = setter.SetYAML(tag, arg)
+				*good = setter.SetYAML(shortTag(tag), arg)
 			}
 		}
 	}
@@ -279,10 +279,10 @@
 func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
 	an, ok := d.doc.anchors[n.value]
 	if !ok {
-		panic("Unknown anchor '" + n.value + "' referenced")
+		fail("Unknown anchor '" + n.value + "' referenced")
 	}
 	if d.aliases[n.value] {
-		panic("Anchor '" + n.value + "' value contains itself")
+		fail("Anchor '" + n.value + "' value contains itself")
 	}
 	d.aliases[n.value] = true
 	good = d.unmarshal(an, out)
@@ -290,23 +290,50 @@
 	return good
 }
 
+var zeroValue reflect.Value
+
+func resetMap(out reflect.Value) {
+	for _, k := range out.MapKeys() {
+		out.SetMapIndex(k, zeroValue)
+	}
+}
+
 var durationType = reflect.TypeOf(time.Duration(0))
 
 func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
 	var tag string
 	var resolved interface{}
 	if n.tag == "" && !n.implicit {
-		tag = "!!str"
+		tag = yaml_STR_TAG
 		resolved = n.value
 	} else {
 		tag, resolved = resolve(n.tag, n.value)
+		if tag == yaml_BINARY_TAG {
+			data, err := base64.StdEncoding.DecodeString(resolved.(string))
+			if err != nil {
+				fail("!!binary value contains invalid base64 data")
+			}
+			resolved = string(data)
+		}
 	}
 	if set := d.setter(tag, &out, &good); set != nil {
 		defer set()
 	}
+	if resolved == nil {
+		if out.Kind() == reflect.Map && !out.CanAddr() {
+			resetMap(out)
+		} else {
+			out.Set(reflect.Zero(out.Type()))
+		}
+		good = true
+		return
+	}
 	switch out.Kind() {
 	case reflect.String:
-		if resolved != nil {
+		if tag == yaml_BINARY_TAG {
+			out.SetString(resolved.(string))
+			good = true
+		} else if resolved != nil {
 			out.SetString(n.value)
 			good = true
 		}
@@ -380,17 +407,11 @@
 			good = true
 		}
 	case reflect.Ptr:
-		switch resolved.(type) {
-		case nil:
-			out.Set(reflect.Zero(out.Type()))
+		if out.Type().Elem() == reflect.TypeOf(resolved) {
+			elem := reflect.New(out.Type().Elem())
+			elem.Elem().Set(reflect.ValueOf(resolved))
+			out.Set(elem)
 			good = true
-		default:
-			if out.Type().Elem() == reflect.TypeOf(resolved) {
-				elem := reflect.New(out.Type().Elem())
-				elem.Elem().Set(reflect.ValueOf(resolved))
-				out.Set(elem)
-				good = true
-			}
 		}
 	}
 	return good
@@ -404,7 +425,7 @@
 }
 
 func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
-	if set := d.setter("!!seq", &out, &good); set != nil {
+	if set := d.setter(yaml_SEQ_TAG, &out, &good); set != nil {
 		defer set()
 	}
 	var iface reflect.Value
@@ -433,7 +454,7 @@
 }
 
 func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
-	if set := d.setter("!!map", &out, &good); set != nil {
+	if set := d.setter(yaml_MAP_TAG, &out, &good); set != nil {
 		defer set()
 	}
 	if out.Kind() == reflect.Struct {
@@ -465,6 +486,13 @@
 		}
 		k := reflect.New(kt).Elem()
 		if d.unmarshal(n.children[i], k) {
+			kkind := k.Kind()
+			if kkind == reflect.Interface {
+				kkind = k.Elem().Kind()
+			}
+			if kkind == reflect.Map || kkind == reflect.Slice {
+				fail(fmt.Sprintf("invalid map key: %#v", k.Interface()))
+			}
 			e := reflect.New(et).Elem()
 			if d.unmarshal(n.children[i+1], e) {
 				out.SetMapIndex(k, e)
@@ -511,28 +539,28 @@
 	case aliasNode:
 		an, ok := d.doc.anchors[n.value]
 		if ok && an.kind != mappingNode {
-			panic(wantMap)
+			fail(wantMap)
 		}
 		d.unmarshal(n, out)
 	case sequenceNode:
 		// Step backwards as earlier nodes take precedence.
-		for i := len(n.children)-1; i >= 0; i-- {
+		for i := len(n.children) - 1; i >= 0; i-- {
 			ni := n.children[i]
 			if ni.kind == aliasNode {
 				an, ok := d.doc.anchors[ni.value]
 				if ok && an.kind != mappingNode {
-					panic(wantMap)
+					fail(wantMap)
 				}
 			} else if ni.kind != mappingNode {
-				panic(wantMap)
+				fail(wantMap)
 			}
 			d.unmarshal(ni, out)
 		}
 	default:
-		panic(wantMap)
+		fail(wantMap)
 	}
 }
 
 func isMerge(n *node) bool {
-	return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == "!!merge" || n.tag == "tag:yaml.org,2002:merge")
+	return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
 }
diff --git a/decode_test.go b/decode_test.go
index d2b45b3..ef3d37f 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -5,6 +5,7 @@
 	"gopkg.in/yaml.v1"
 	"math"
 	"reflect"
+	"strings"
 	"time"
 )
 
@@ -316,7 +317,10 @@
 		map[string]*string{"foo": new(string)},
 	}, {
 		"foo: null",
-		map[string]string{},
+		map[string]string{"foo": ""},
+	}, {
+		"foo: null",
+		map[string]interface{}{"foo": nil},
 	},
 
 	// Ignored field
@@ -371,6 +375,30 @@
 		"a: 3s",
 		map[string]time.Duration{"a": 3 * time.Second},
 	},
+
+	// Issue #24.
+	{
+		"a: <foo>",
+		map[string]string{"a": "<foo>"},
+	},
+
+	// Base 60 floats are obsolete and unsupported.
+	{
+		"a: 1:1\n",
+		map[string]string{"a": "1:1"},
+	},
+
+	// Binary data.
+	{
+		"a: !!binary gIGC\n",
+		map[string]string{"a": "\x80\x81\x82"},
+	}, {
+		"a: !!binary |\n  " + strings.Repeat("kJCQ", 17) + "kJ\n  CQ\n",
+		map[string]string{"a": strings.Repeat("\x90", 54)},
+	}, {
+		"a: !!binary |\n  " + strings.Repeat("A", 70) + "\n  ==\n",
+		map[string]string{"a": strings.Repeat("\x00", 52)},
+	},
 }
 
 type inlineB struct {
@@ -418,12 +446,15 @@
 var unmarshalErrorTests = []struct {
 	data, error string
 }{
-	{"v: !!float 'error'", "YAML error: Can't decode !!str 'error' as a !!float"},
+	{"v: !!float 'error'", "YAML error: cannot 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"},
 	{"a: *b\n", "YAML error: Unknown anchor 'b' referenced"},
 	{"a: &a\n  b: *a\n", "YAML error: Anchor 'a' value contains itself"},
 	{"value: -", "YAML error: block sequence entries are not allowed in this context"},
+	{"a: !!binary ==", "YAML error: !!binary value contains invalid base64 data"},
+	{"{[.]}", `YAML error: invalid map key: \[\]interface \{\}\{"\."\}`},
+	{"{{.}}", `YAML error: invalid map key: map\[interface\ \{\}\]interface \{\}\{".":interface \{\}\(nil\)\}`},
 }
 
 func (s *S) TestUnmarshalErrors(c *C) {
@@ -618,6 +649,30 @@
 	}
 }
 
+var unmarshalNullTests = []func() interface{}{
+	func() interface{} { var v interface{}; v = "v"; return &v },
+	func() interface{} { var s = "s"; return &s },
+	func() interface{} { var s = "s"; sptr := &s; return &sptr },
+	func() interface{} { var i = 1; return &i },
+	func() interface{} { var i = 1; iptr := &i; return &iptr },
+	func() interface{} { m := map[string]int{"s": 1}; return &m },
+	func() interface{} { m := map[string]int{"s": 1}; return m },
+}
+
+func (s *S) TestUnmarshalNull(c *C) {
+	for _, test := range unmarshalNullTests {
+		item := test()
+		zero := reflect.Zero(reflect.TypeOf(item).Elem()).Interface()
+		err := yaml.Unmarshal([]byte("null"), item)
+		c.Assert(err, IsNil)
+		if reflect.TypeOf(item).Kind() == reflect.Map {
+			c.Assert(reflect.ValueOf(item).Interface(), DeepEquals, reflect.MakeMap(reflect.TypeOf(item)).Interface())
+		} else {
+			c.Assert(reflect.ValueOf(item).Elem().Interface(), DeepEquals, zero)
+		}
+	}
+}
+
 //var data []byte
 //func init() {
 //	var err error
diff --git a/emitterc.go b/emitterc.go
index 542ffd2..9b3dc4a 100644
--- a/emitterc.go
+++ b/emitterc.go
@@ -973,8 +973,8 @@
 		if bytes.HasPrefix(tag, tag_directive.prefix) {
 			emitter.tag_data.handle = tag_directive.handle
 			emitter.tag_data.suffix = tag[len(tag_directive.prefix):]
+			return true
 		}
-		return true
 	}
 	emitter.tag_data.suffix = tag
 	return true
@@ -1279,6 +1279,9 @@
 			for k := 0; k < w; k++ {
 				octet := value[i]
 				i++
+				if !put(emitter, '%') {
+					return false
+				}
 
 				c := octet >> 4
 				if c < 10 {
diff --git a/encode.go b/encode.go
index 1d928b0..0b9048d 100644
--- a/encode.go
+++ b/encode.go
@@ -2,8 +2,10 @@
 
 import (
 	"reflect"
+	"regexp"
 	"sort"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -50,14 +52,19 @@
 		if msg == "" {
 			msg = "Unknown problem generating YAML content"
 		}
-		panic(msg)
+		fail(msg)
 	}
 }
 
 func (e *encoder) marshal(tag string, in reflect.Value) {
+	if !in.IsValid() {
+		e.nilv()
+		return
+	}
 	var value interface{}
 	if getter, ok := in.Interface().(Getter); ok {
 		tag, value = getter.GetYAML()
+		tag = longTag(tag)
 		if value == nil {
 			e.nilv()
 			return
@@ -98,7 +105,7 @@
 	case reflect.Bool:
 		e.boolv(tag, in)
 	default:
-		panic("Can't marshal type yet: " + in.Type().String())
+		panic("Can't marshal type: " + in.Type().String())
 	}
 }
 
@@ -167,11 +174,46 @@
 	e.emit()
 }
 
+// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
+//
+// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
+// in YAML 1.2 and by this package, but these should be marshalled quoted for
+// the time being for compatibility with other parsers.
+func isBase60Float(s string) (result bool) {
+	// Fast path.
+	if s == "" {
+		return false
+	}
+	c := s[0]
+	if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
+		return false
+	}
+	// Do the full match.
+	return base60float.MatchString(s)
+}
+
+// From http://yaml.org/type/float.html, except the regular expression there
+// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
+var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
+
 func (e *encoder) stringv(tag string, in reflect.Value) {
 	var style yaml_scalar_style_t
 	s := in.String()
-	if rtag, _ := resolve("", s); rtag != "!!str" {
+	rtag, rs := resolve("", s)
+	if rtag == yaml_BINARY_TAG {
+		if tag == "" || tag == yaml_STR_TAG {
+			tag = rtag
+			s = rs.(string)
+		} else if tag == yaml_BINARY_TAG {
+			fail("explicitly tagged !!binary data must be base64-encoded")
+		} else {
+			fail("cannot marshal invalid UTF-8 data as " + shortTag(tag))
+		}
+	}
+	if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) {
 		style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
+	} else if strings.Contains(s, "\n") {
+		style = yaml_LITERAL_SCALAR_STYLE
 	} else {
 		style = yaml_PLAIN_SCALAR_STYLE
 	}
@@ -218,9 +260,6 @@
 
 func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
 	implicit := tag == ""
-	if !implicit {
-		style = yaml_PLAIN_SCALAR_STYLE
-	}
 	e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
 	e.emit()
 }
diff --git a/encode_test.go b/encode_test.go
index c7461d5..c9febc2 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -2,12 +2,13 @@
 
 import (
 	"fmt"
-	"gopkg.in/yaml.v1"
-	. "gopkg.in/check.v1"
 	"math"
 	"strconv"
 	"strings"
 	"time"
+
+	. "gopkg.in/check.v1"
+	"gopkg.in/yaml.v1"
 )
 
 var marshalIntTest = 123
@@ -17,6 +18,9 @@
 	data  string
 }{
 	{
+		nil,
+		"null\n",
+	}, {
 		&struct{}{},
 		"{}\n",
 	}, {
@@ -87,7 +91,7 @@
 		"v:\n- A\n- B\n",
 	}, {
 		map[string][]string{"v": []string{"A", "B\nC"}},
-		"v:\n- A\n- 'B\n\n  C'\n",
+		"v:\n- A\n- |-\n  B\n  C\n",
 	}, {
 		map[string][]interface{}{"v": []interface{}{"A", 1, map[string][]int{"B": []int{2, 3}}}},
 		"v:\n- A\n- 1\n- B:\n  - 2\n  - 3\n",
@@ -219,6 +223,40 @@
 		map[string]time.Duration{"a": 3 * time.Second},
 		"a: 3s\n",
 	},
+
+	// Issue #24: bug in map merging logic.
+	{
+		map[string]string{"a": "<foo>"},
+		"a: <foo>\n",
+	},
+
+	// Issue #34: marshal unsupported base 60 floats quoted for compatibility
+	// with old YAML 1.1 parsers.
+	{
+		map[string]string{"a": "1:1"},
+		"a: \"1:1\"\n",
+	},
+
+	// Binary data.
+	{
+		map[string]string{"a": "\x00"},
+		"a: \"\\0\"\n",
+	}, {
+		map[string]string{"a": "\x80\x81\x82"},
+		"a: !!binary gIGC\n",
+	}, {
+		map[string]string{"a": strings.Repeat("\x90", 54)},
+		"a: !!binary |\n  " + strings.Repeat("kJCQ", 17) + "kJ\n  CQ\n",
+	}, {
+		map[string]interface{}{"a": typeWithGetter{"!!str", "\x80\x81\x82"}},
+		"a: !!binary gIGC\n",
+	},
+
+	// Escaping of tags.
+	{
+		map[string]interface{}{"a": typeWithGetter{"foo!bar", 1}},
+		"a: !<foo%21bar> 1\n",
+	},
 }
 
 func (s *S) TestMarshal(c *C) {
@@ -232,20 +270,29 @@
 var marshalErrorTests = []struct {
 	value interface{}
 	error string
-}{
-	{
-		&struct {
-			B       int
-			inlineB ",inline"
-		}{1, inlineB{2, inlineC{3}}},
-		`Duplicated key 'b' in struct struct \{ B int; .*`,
-	},
-}
+	panic string
+}{{
+	value: &struct {
+		B       int
+		inlineB ",inline"
+	}{1, inlineB{2, inlineC{3}}},
+	panic: `Duplicated key 'b' in struct struct \{ B int; .*`,
+}, {
+	value: typeWithGetter{"!!binary", "\x80"},
+	error: "YAML error: explicitly tagged !!binary data must be base64-encoded",
+}, {
+	value: typeWithGetter{"!!float", "\x80"},
+	error: `YAML error: cannot marshal invalid UTF-8 data as !!float`,
+}}
 
 func (s *S) TestMarshalErrors(c *C) {
 	for _, item := range marshalErrorTests {
-		_, err := yaml.Marshal(item.value)
-		c.Assert(err, ErrorMatches, item.error)
+		if item.panic != "" {
+			c.Assert(func() { yaml.Marshal(item.value) }, PanicMatches, item.panic)
+		} else {
+			_, err := yaml.Marshal(item.value)
+			c.Assert(err, ErrorMatches, item.error)
+		}
 	}
 }
 
diff --git a/resolve.go b/resolve.go
index fdc4909..06c698a 100644
--- a/resolve.go
+++ b/resolve.go
@@ -1,9 +1,12 @@
 package yaml
 
 import (
+	"encoding/base64"
+	"fmt"
 	"math"
 	"strconv"
 	"strings"
+	"unicode/utf8"
 )
 
 // TODO: merge, timestamps, base 60 floats, omap.
@@ -27,25 +30,24 @@
 		t[int(c)] = 'M' // In map
 	}
 	t[int('.')] = '.' // Float (potentially in map)
-	t[int('<')] = '<' // Merge
 
 	var resolveMapList = []struct {
 		v   interface{}
 		tag string
 		l   []string
 	}{
-		{true, "!!bool", []string{"y", "Y", "yes", "Yes", "YES"}},
-		{true, "!!bool", []string{"true", "True", "TRUE"}},
-		{true, "!!bool", []string{"on", "On", "ON"}},
-		{false, "!!bool", []string{"n", "N", "no", "No", "NO"}},
-		{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.Inf(+1), "!!float", []string{".inf", ".Inf", ".INF"}},
-		{math.Inf(+1), "!!float", []string{"+.inf", "+.Inf", "+.INF"}},
-		{math.Inf(-1), "!!float", []string{"-.inf", "-.Inf", "-.INF"}},
-		{"<<", "!!merge", []string{"<<"}},
+		{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
+		{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
+		{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
+		{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
+		{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
+		{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
+		{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
+		{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
+		{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
+		{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
+		{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
+		{"<<", yaml_MERGE_TAG, []string{"<<"}},
 	}
 
 	m := resolveMap
@@ -59,90 +61,130 @@
 const longTagPrefix = "tag:yaml.org,2002:"
 
 func shortTag(tag string) string {
+	// TODO This can easily be made faster and produce less garbage.
 	if strings.HasPrefix(tag, longTagPrefix) {
 		return "!!" + tag[len(longTagPrefix):]
 	}
 	return tag
 }
 
+func longTag(tag string) string {
+	if strings.HasPrefix(tag, "!!") {
+		return longTagPrefix + tag[2:]
+	}
+	return tag
+}
+
 func resolvableTag(tag string) bool {
 	switch tag {
-	case "", "!!str", "!!bool", "!!int", "!!float", "!!null":
+	case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG:
 		return true
 	}
 	return false
 }
 
 func resolve(tag string, in string) (rtag string, out interface{}) {
-	tag = shortTag(tag)
 	if !resolvableTag(tag) {
 		return tag, in
 	}
 
 	defer func() {
-		if tag != "" && tag != rtag {
-			panic("Can't decode " + rtag + " '" + in + "' as a " + tag)
+		switch tag {
+		case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
+			return
 		}
+		fail(fmt.Sprintf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag)))
 	}()
 
-	if in == "" {
-		return "!!null", nil
+	// Any data is accepted as a !!str or !!binary.
+	// Otherwise, the prefix is enough of a hint about what it might be.
+	hint := byte('N')
+	if in != "" {
+		hint = resolveTable[in[0]]
 	}
-
-	c := resolveTable[in[0]]
-	if c == 0 {
-		// It's a string for sure. Nothing to do.
-		return "!!str", in
-	}
-
-	// Handle things we can lookup in a map.
-	if item, ok := resolveMap[in]; ok {
-		return item.tag, item.value
-	}
-
-	switch c {
-	case 'M':
-		// We've already checked the map above.
-
-	case '.':
-		// Not in the map, so maybe a normal float.
-		floatv, err := strconv.ParseFloat(in, 64)
-		if err == nil {
-			return "!!float", floatv
+	if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
+		// Handle things we can lookup in a map.
+		if item, ok := resolveMap[in]; ok {
+			return item.tag, item.value
 		}
-	// XXX Handle base 60 floats here (WTF!)
 
-	case 'D', 'S':
-		// Int, float, or timestamp.
-		plain := strings.Replace(in, "_", "", -1)
-		intv, err := strconv.ParseInt(plain, 0, 64)
-		if err == nil {
-			if intv == int64(int(intv)) {
-				return "!!int", int(intv)
-			} else {
-				return "!!int", intv
-			}
-		}
-		floatv, err := strconv.ParseFloat(plain, 64)
-		if err == nil {
-			return "!!float", floatv
-		}
-		if strings.HasPrefix(plain, "0b") {
-			intv, err := strconv.ParseInt(plain[2:], 2, 64)
+		// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
+		// are purposefully unsupported here. They're still quoted on
+		// the way out for compatibility with other parser, though.
+
+		switch hint {
+		case 'M':
+			// We've already checked the map above.
+
+		case '.':
+			// Not in the map, so maybe a normal float.
+			floatv, err := strconv.ParseFloat(in, 64)
 			if err == nil {
-				return "!!int", int(intv)
+				return yaml_FLOAT_TAG, floatv
 			}
-		} else if strings.HasPrefix(plain, "-0b") {
-			intv, err := strconv.ParseInt(plain[3:], 2, 64)
-			if err == nil {
-				return "!!int", -int(intv)
-			}
-		}
-	// XXX Handle timestamps here.
 
-	default:
-		panic("resolveTable item not yet handled: " +
-			string([]byte{c}) + " (with " + in + ")")
+		case 'D', 'S':
+			// Int, float, or timestamp.
+			plain := strings.Replace(in, "_", "", -1)
+			intv, err := strconv.ParseInt(plain, 0, 64)
+			if err == nil {
+				if intv == int64(int(intv)) {
+					return yaml_INT_TAG, int(intv)
+				} else {
+					return yaml_INT_TAG, intv
+				}
+			}
+			floatv, err := strconv.ParseFloat(plain, 64)
+			if err == nil {
+				return yaml_FLOAT_TAG, floatv
+			}
+			if strings.HasPrefix(plain, "0b") {
+				intv, err := strconv.ParseInt(plain[2:], 2, 64)
+				if err == nil {
+					return yaml_INT_TAG, int(intv)
+				}
+			} else if strings.HasPrefix(plain, "-0b") {
+				intv, err := strconv.ParseInt(plain[3:], 2, 64)
+				if err == nil {
+					return yaml_INT_TAG, -int(intv)
+				}
+			}
+			// XXX Handle timestamps here.
+
+		default:
+			panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
+		}
 	}
-	return "!!str", in
+	if tag == yaml_BINARY_TAG {
+		return yaml_BINARY_TAG, in
+	}
+	if utf8.ValidString(in) {
+		return yaml_STR_TAG, in
+	}
+	return yaml_BINARY_TAG, encodeBase64(in)
+}
+
+// encodeBase64 encodes s as base64 that is broken up into multiple lines
+// as appropriate for the resulting length.
+func encodeBase64(s string) string {
+	const lineLen = 70
+	encLen := base64.StdEncoding.EncodedLen(len(s))
+	lines := encLen/lineLen + 1
+	buf := make([]byte, encLen*2+lines)
+	in := buf[0:encLen]
+	out := buf[encLen:]
+	base64.StdEncoding.Encode(in, []byte(s))
+	k := 0
+	for i := 0; i < len(in); i += lineLen {
+		j := i + lineLen
+		if j > len(in) {
+			j = len(in)
+		}
+		k += copy(out[k:], in[i:j])
+		if lines > 1 {
+			out[k] = '\n'
+			k++
+		}
+	}
+	return string(out[:k])
 }
diff --git a/yaml.go b/yaml.go
index f4e03bf..f1c390e 100644
--- a/yaml.go
+++ b/yaml.go
@@ -10,23 +10,20 @@
 	"errors"
 	"fmt"
 	"reflect"
-	"runtime"
 	"strings"
 	"sync"
 )
 
+type yamlError string
+
+func fail(msg string) {
+	panic(yamlError(msg))
+}
+
 func handleErr(err *error) {
 	if r := recover(); r != nil {
-		if _, ok := r.(runtime.Error); ok {
-			panic(r)
-		} else if _, ok := r.(*reflect.ValueError); ok {
-			panic(r)
-		} else if _, ok := r.(externalPanic); ok {
-			panic(r)
-		} else if s, ok := r.(string); ok {
-			*err = errors.New("YAML error: " + s)
-		} else if e, ok := r.(error); ok {
-			*err = e
+		if e, ok := r.(yamlError); ok {
+			*err = errors.New("YAML error: " + string(e))
 		} else {
 			panic(r)
 		}
@@ -91,7 +88,11 @@
 	defer p.destroy()
 	node := p.parse()
 	if node != nil {
-		d.unmarshal(node, reflect.ValueOf(out))
+		v := reflect.ValueOf(out)
+		if v.Kind() == reflect.Ptr && !v.IsNil() {
+			v = v.Elem()
+		}
+		d.unmarshal(node, v)
 	}
 	return nil
 }
@@ -174,12 +175,6 @@
 var structMap = make(map[reflect.Type]*structInfo)
 var fieldMapMutex sync.RWMutex
 
-type externalPanic string
-
-func (e externalPanic) String() string {
-	return string(e)
-}
-
 func getStructInfo(st reflect.Type) (*structInfo, error) {
 	fieldMapMutex.RLock()
 	sinfo, found := structMap[st]
@@ -220,8 +215,7 @@
 				case "inline":
 					inline = true
 				default:
-					msg := fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st)
-					panic(externalPanic(msg))
+					return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
 				}
 			}
 			tag = fields[0]
@@ -229,6 +223,7 @@
 
 		if inline {
 			switch field.Type.Kind() {
+			// TODO: Implement support for inline maps.
 			//case reflect.Map:
 			//	if inlineMap >= 0 {
 			//		return nil, errors.New("Multiple ,inline maps in struct " + st.String())
@@ -256,8 +251,8 @@
 					fieldsList = append(fieldsList, finfo)
 				}
 			default:
-				//panic("Option ,inline needs a struct value or map field")
-				panic("Option ,inline needs a struct value field")
+				//return nil, errors.New("Option ,inline needs a struct value or map field")
+				return nil, errors.New("Option ,inline needs a struct value field")
 			}
 			continue
 		}
diff --git a/yamlh.go b/yamlh.go
index 6624d6c..4b020b1 100644
--- a/yamlh.go
+++ b/yamlh.go
@@ -294,6 +294,10 @@
 	yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
 	yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
 
+	// Not in original libyaml.
+	yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
+	yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
+
 	yaml_DEFAULT_SCALAR_TAG   = yaml_STR_TAG // The default scalar tag is !!str.
 	yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
 	yaml_DEFAULT_MAPPING_TAG  = yaml_MAP_TAG // The default mapping tag is !!map.