Skip to content

Commit

Permalink
Resolve Node tags on decoding and use short tags.
Browse files Browse the repository at this point in the history
This has several good consequences, such as ensuring the value
won't change type on manipulation unless that's intended, and
preventing reparsing of the value repeatedly to understand and
decode it.
  • Loading branch information
niemeyer committed Mar 23, 2019
1 parent 01167db commit 891da3a
Show file tree
Hide file tree
Showing 5 changed files with 577 additions and 299 deletions.
123 changes: 68 additions & 55 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ const (
type Style uint32

const (
DoubleQuotedStyle Style = 1 << iota
TaggedStyle Style = 1 << iota
DoubleQuotedStyle
SingleQuotedStyle
LiteralStyle
FoldedStyle
Expand All @@ -47,52 +48,45 @@ type Node struct {
Footer string
}

func (n *Node) implicit() bool {
return n.Style&(SingleQuotedStyle|DoubleQuotedStyle) == 0 && (n.Tag == "" || n.Tag == "!")
func (n *Node) LongTag() string {
return longTag(n.ShortTag())
}

// TODO Quite some garbage being generated by these common functions.

func (n *Node) ShortTag() string {
tag := n.LongTag()
if strings.HasPrefix(tag, longTagPrefix) {
return "!!" + tag[len(longTagPrefix):]
if n.indicatedString() {
return strTag
}
return tag
}

func (n *Node) LongTag() string {
if n.Tag == "" || n.Tag == "!" {
switch n.Kind {
case MappingNode:
return yaml_MAP_TAG
return mapTag
case SequenceNode:
return yaml_SEQ_TAG
return seqTag
case AliasNode:
if n.Alias != nil {
return n.Alias.LongTag()
return n.Alias.ShortTag()
}
case ScalarNode:
if n.Style&(SingleQuotedStyle|DoubleQuotedStyle) != 0 {
return yaml_STR_TAG
}
tag, _ := resolve("", n.Value)
return tag
}
return ""

} else if strings.HasPrefix(n.Tag, "!!") {
return longTagPrefix + n.Tag[2:]
}
return n.Tag
return shortTag(n.Tag)
}

func (n *Node) indicatedString() bool {
return n.Kind == ScalarNode &&
(shortTag(n.Tag) == strTag ||
(n.Tag == "" || n.Tag == "!") && n.Style&(SingleQuotedStyle|DoubleQuotedStyle|LiteralStyle|FoldedStyle) != 0)
}

func (n *Node) SetString(s string) {
n.Kind = ScalarNode
n.Value = s
if strings.Contains(s, "\n") {
n.Style = LiteralStyle
} else if n.LongTag() != "tag:yaml.org,2002:str" {
} else if n.ShortTag() != strTag {
n.Style = DoubleQuotedStyle
}
}
Expand Down Expand Up @@ -228,9 +222,21 @@ func (p *parser) parse() *Node {
}
}

func (p *parser) node(kind NodeKind) *Node {
func (p *parser) node(kind NodeKind, defaultTag, tag, value string) *Node {
var style Style
if tag != "" && tag != "!" {
tag = shortTag(tag)
style = TaggedStyle
} else if defaultTag != "" {
tag = defaultTag
} else if kind == ScalarNode {
tag, _ = resolve("", value)
}
return &Node{
Kind: kind,
Tag: tag,
Value: value,
Style: style,
Line: p.event.start_mark.line + 1,
Column: p.event.start_mark.column + 1,
Header: string(p.event.header_comment),
Expand All @@ -246,7 +252,7 @@ func (p *parser) parseChild(parent *Node) *Node {
}

func (p *parser) document() *Node {
n := p.node(DocumentNode)
n := p.node(DocumentNode, "", "", "")
p.doc = n
p.expect(yaml_DOCUMENT_START_EVENT)
p.parseChild(n)
Expand All @@ -258,8 +264,7 @@ func (p *parser) document() *Node {
}

func (p *parser) alias() *Node {
n := p.node(AliasNode)
n.Value = string(p.event.anchor)
n := p.node(AliasNode, "", "", string(p.event.anchor))
n.Alias = p.anchors[n.Value]
if n.Alias == nil {
failf("unknown anchor '%s' referenced", n.Value)
Expand All @@ -269,30 +274,39 @@ func (p *parser) alias() *Node {
}

func (p *parser) scalar() *Node {
n := p.node(ScalarNode)
n.Value = string(p.event.value)
n.Tag = string(p.event.tag)
style := p.event.scalar_style()
var parsedStyle = p.event.scalar_style()
var nodeStyle Style
switch {
case style&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0:
n.Style = DoubleQuotedStyle
case style&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0:
n.Style = SingleQuotedStyle
case style&yaml_LITERAL_SCALAR_STYLE != 0:
n.Style = LiteralStyle
case style&yaml_FOLDED_SCALAR_STYLE != 0:
n.Style = FoldedStyle
case parsedStyle&yaml_DOUBLE_QUOTED_SCALAR_STYLE != 0:
nodeStyle = DoubleQuotedStyle
case parsedStyle&yaml_SINGLE_QUOTED_SCALAR_STYLE != 0:
nodeStyle = SingleQuotedStyle
case parsedStyle&yaml_LITERAL_SCALAR_STYLE != 0:
nodeStyle = LiteralStyle
case parsedStyle&yaml_FOLDED_SCALAR_STYLE != 0:
nodeStyle = FoldedStyle
}
var nodeValue = string(p.event.value)
var nodeTag = string(p.event.tag)
var defaultTag string
if nodeStyle == 0 {
if nodeValue == "<<" {
defaultTag = mergeTag
}
} else {
defaultTag = strTag
}
n := p.node(ScalarNode, defaultTag, nodeTag, nodeValue)
n.Style |= nodeStyle
p.anchor(n, p.event.anchor)
p.expect(yaml_SCALAR_EVENT)
return n
}

func (p *parser) sequence() *Node {
n := p.node(SequenceNode)
n.Tag = string(p.event.tag)
n := p.node(SequenceNode, seqTag, string(p.event.tag), "")
if p.event.sequence_style()&yaml_FLOW_SEQUENCE_STYLE != 0 {
n.Style = FlowStyle
n.Style |= FlowStyle
}
p.anchor(n, p.event.anchor)
p.expect(yaml_SEQUENCE_START_EVENT)
Expand All @@ -306,8 +320,7 @@ func (p *parser) sequence() *Node {
}

func (p *parser) mapping() *Node {
n := p.node(MappingNode)
n.Tag = string(p.event.tag)
n := p.node(MappingNode, mapTag, string(p.event.tag), "")
if p.event.mapping_style()&yaml_FLOW_MAPPING_STYLE != 0 {
n.Style |= FlowStyle
}
Expand Down Expand Up @@ -367,7 +380,7 @@ func (d *decoder) terror(n *Node, tag string, out reflect.Value) {
tag = n.Tag
}
value := n.Value
if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
if tag != seqTag && tag != mapTag {
if len(value) > 10 {
value = " `" + value[:7] + "...`"
} else {
Expand Down Expand Up @@ -414,7 +427,7 @@ func (d *decoder) callObsoleteUnmarshaler(n *Node, u obsoleteUnmarshaler) (good
//
// If n holds a null value, prepare returns before doing anything.
func (d *decoder) prepare(n *Node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
if n.Tag == yaml_NULL_TAG || n.Kind == ScalarNode && n.Tag == "" && (n.Value == "null" || n.Value == "~" || n.Value == "" && n.implicit()) {
if n.ShortTag() == nullTag {
return out, false, false
}
again := true
Expand Down Expand Up @@ -501,12 +514,12 @@ func resetMap(out reflect.Value) {
func (d *decoder) scalar(n *Node, out reflect.Value) bool {
var tag string
var resolved interface{}
if n.Tag == "" && !n.implicit() {
tag = yaml_STR_TAG
if n.indicatedString() {
tag = strTag
resolved = n.Value
} else {
tag, resolved = resolve(n.Tag, n.Value)
if tag == yaml_BINARY_TAG {
if tag == binaryTag {
data, err := base64.StdEncoding.DecodeString(resolved.(string))
if err != nil {
failf("!!binary value contains invalid base64 data")
Expand All @@ -533,7 +546,7 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
if ok {
var text []byte
if tag == yaml_BINARY_TAG {
if tag == binaryTag {
text = []byte(resolved.(string))
} else {
// We let any value be unmarshaled into TextUnmarshaler.
Expand All @@ -550,7 +563,7 @@ func (d *decoder) scalar(n *Node, out reflect.Value) bool {
}
switch out.Kind() {
case reflect.String:
if tag == yaml_BINARY_TAG {
if tag == binaryTag {
out.SetString(resolved.(string))
return true
}
Expand Down Expand Up @@ -695,7 +708,7 @@ func (d *decoder) sequence(n *Node, out reflect.Value) (good bool) {
iface = out
out = settableValueOf(make([]interface{}, l))
default:
d.terror(n, yaml_SEQ_TAG, out)
d.terror(n, seqTag, out)
return false
}
et := out.Type().Elem()
Expand Down Expand Up @@ -748,7 +761,7 @@ func (d *decoder) mapping(n *Node, out reflect.Value) (good bool) {
}
iface.Set(out)
default:
d.terror(n, yaml_MAP_TAG, out)
d.terror(n, mapTag, out)
return false
}

Expand Down Expand Up @@ -800,7 +813,7 @@ func isStringMap(n *Node) bool {
}
l := len(n.Children)
for i := 0; i < l; i++ {
if n.Children[i].LongTag() != yaml_STR_TAG {
if n.Children[i].ShortTag() != strTag {
return false
}
}
Expand Down Expand Up @@ -897,5 +910,5 @@ func (d *decoder) merge(n *Node, out reflect.Value) {
}

func isMerge(n *Node) bool {
return n.Kind == ScalarNode && n.Value == "<<" && (n.implicit() || n.Tag == yaml_MERGE_TAG)
return n.Kind == ScalarNode && n.Value == "<<" && (n.Tag == "" || n.Tag == "!" || shortTag(n.Tag) == mergeTag)
}
3 changes: 3 additions & 0 deletions decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,9 @@ var unmarshalTests = []struct {
}, {
"a: {b: c}",
&struct{ A *struct{ B string } }{&struct{ B string }{"c"}},
}, {
"a: 'null'",
&struct{ A *unmarshalerType }{&unmarshalerType{"null"}},
}, {
"a: {b: c}",
&struct{ A map[string]string }{map[string]string{"b": "c"}},
Expand Down
Loading

0 comments on commit 891da3a

Please sign in to comment.