Merge pull request #313 from rogpeppe/020-quote-merge-key

quote "<<" keys on output
diff --git a/decode.go b/decode.go
index c8eac16..8a3ed78 100644
--- a/decode.go
+++ b/decode.go
@@ -234,6 +234,7 @@
 	ifaceType      = defaultMapType.Elem()
 	timeType       = reflect.TypeOf(time.Time{})
 	ptrTimeType    = reflect.TypeOf(&time.Time{})
+	mergeTagType   = reflect.TypeOf(MergeTag)
 )
 
 func newDecoder(strict bool) *decoder {
@@ -760,5 +761,11 @@
 }
 
 func isMerge(n *node) bool {
-	return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
+	// Quick test so we avoid the overhead of calling resolve
+	// unless we're pretty sure it can be a merge node.
+	if n.kind != scalarNode || n.value != "<<" {
+		return false
+	}
+	_, v := resolve(n.tag, n.value)
+	return v == MergeTag
 }
diff --git a/decode_test.go b/decode_test.go
index 7e1e8b3..40f60d5 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -412,6 +412,11 @@
 		"v: ! test",
 		map[string]interface{}{"v": "test"},
 	},
+	// Non-specific tag with resolvable item.
+	{
+		"v: ! 99",
+		map[string]interface{}{"v": "99"},
+	},
 
 	// Anchors and aliases.
 	{
@@ -1080,6 +1085,7 @@
   # Inlined map in sequence
   << : [ *CENTER, {"r": 10} ]
   label: center/big
+
 `
 
 func (s *S) TestMerge(c *C) {
diff --git a/encode.go b/encode.go
index 1e730ef..249aeee 100644
--- a/encode.go
+++ b/encode.go
@@ -126,9 +126,12 @@
 			e.marshal(tag, in.Elem())
 		}
 	case reflect.Struct:
-		if in.Type() == timeType {
+		switch in.Type() {
+		case timeType:
 			e.timev(tag, in)
-		} else {
+		case mergeTagType:
+			e.mergeTagv(tag)
+		default:
 			e.structv(tag, in)
 		}
 	case reflect.Slice:
@@ -334,6 +337,10 @@
 	e.emitScalar(t.Format(time.RFC3339Nano), "", tag, yaml_PLAIN_SCALAR_STYLE)
 }
 
+func (e *encoder) mergeTagv(tag string) {
+	e.emitScalar("<<", "", tag, yaml_PLAIN_SCALAR_STYLE)
+}
+
 func (e *encoder) floatv(tag string, in reflect.Value) {
 	s := strconv.FormatFloat(in.Float(), 'g', -1, 64)
 	switch s {
diff --git a/encode_test.go b/encode_test.go
index 2e78411..47012a2 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -356,6 +356,17 @@
 		map[string]string{"a": "你好 #comment"},
 		"a: '你好 #comment'\n",
 	},
+
+	// << should be quoted so that it's not treated as a merge key.
+	{
+		map[string]string{"<<": "x"},
+		"\"<<\": x\n",
+	},
+	// Explicit merge tag key is left unquoted.
+	{
+		map[interface{}]interface{}{yaml.MergeTag: map[string]string{"a": "b"}},
+		"<<:\n  a: b\n",
+	},
 }
 
 func (s *S) TestMarshal(c *C) {
diff --git a/resolve.go b/resolve.go
index ea90bd5..086c9f4 100644
--- a/resolve.go
+++ b/resolve.go
@@ -14,20 +14,28 @@
 	tag   string
 }
 
+// MergeTag is the value used for a merge tag (!!merge "<<").
+// When marshaled as a map key, the << will not be quoted,
+// which makes it possible to include merge tags explicitly
+// when encoding YAML values.
+var MergeTag = mergeTag{}
+
+type mergeTag struct{}
+
 var resolveTable = make([]byte, 256)
 var resolveMap = make(map[string]resolveMapItem)
 
 func init() {
 	t := resolveTable
-	t[int('+')] = 'S' // Sign
-	t[int('-')] = 'S'
+	t['+'] = 'S' // Sign
+	t['-'] = 'S'
 	for _, c := range "0123456789" {
-		t[int(c)] = 'D' // Digit
+		t[c] = 'D' // Digit
 	}
-	for _, c := range "yYnNtTfFoO~" {
-		t[int(c)] = 'M' // In map
+	for _, c := range "yYnNtTfFoO~<" {
+		t[c] = 'M' // In resolveMap
 	}
-	t[int('.')] = '.' // Float (potentially in map)
+	t['.'] = '.' // Float (potentially in map)
 
 	var resolveMapList = []struct {
 		v   interface{}
@@ -45,7 +53,7 @@
 		{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{"<<"}},
+		{MergeTag, yaml_MERGE_TAG, []string{"<<"}},
 	}
 
 	m := resolveMap
@@ -75,7 +83,7 @@
 
 func resolvableTag(tag string) bool {
 	switch tag {
-	case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
+	case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG, yaml_MERGE_TAG:
 		return true
 	}
 	return false