Skip to content

Commit

Permalink
Fix parsing of directive value (#578)
Browse files Browse the repository at this point in the history
* fix directive

* fix tag value handling

* remove passed test cases

* add comment
  • Loading branch information
goccy authored Dec 9, 2024
1 parent 264b167 commit 438f2d0
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 78 deletions.
38 changes: 30 additions & 8 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1726,8 +1726,12 @@ func (n *AliasNode) MarshalYAML() ([]byte, error) {
// DirectiveNode type of directive node
type DirectiveNode struct {
*BaseNode
// Start is '%' token.
Start *token.Token
Value Node
// Name is directive name e.g.) "YAML" or "TAG".
Name Node
// Values is directive values e.g.) "1.2" or "!!" and "tag:clarkevans.com,2002:app/".
Values []Node
}

// Read implements (io.Reader).Read
Expand All @@ -1745,14 +1749,21 @@ func (n *DirectiveNode) GetToken() *token.Token {

// AddColumn add column number to child nodes recursively
func (n *DirectiveNode) AddColumn(col int) {
if n.Value != nil {
n.Value.AddColumn(col)
if n.Name != nil {
n.Name.AddColumn(col)
}
for _, value := range n.Values {
value.AddColumn(col)
}
}

// String directive to text
func (n *DirectiveNode) String() string {
return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
values := make([]string, 0, len(n.Values))
for _, val := range n.Values {
values = append(values, val.String())
}
return strings.Join(append([]string{"%" + n.Name.String()}, values...), " ")
}

// MarshalYAML encodes to a YAML text
Expand All @@ -1763,8 +1774,9 @@ func (n *DirectiveNode) MarshalYAML() ([]byte, error) {
// TagNode type of tag node
type TagNode struct {
*BaseNode
Start *token.Token
Value Node
Directive *DirectiveNode
Start *token.Token
Value Node
}

func (n *TagNode) GetValue() any {
Expand Down Expand Up @@ -1940,7 +1952,10 @@ func Walk(v Visitor, node Node) {
Walk(v, n.Value)
case *DirectiveNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
Walk(v, n.Name)
for _, value := range n.Values {
Walk(v, value)
}
case *TagNode:
walkComment(v, n.BaseNode)
Walk(v, n.Value)
Expand Down Expand Up @@ -2026,7 +2041,14 @@ func (f *parentFinder) walk(parent, node Node) Node {
case *LiteralNode:
return f.walk(node, n.Value)
case *DirectiveNode:
return f.walk(node, n.Value)
if found := f.walk(node, n.Name); found != nil {
return found
}
for _, value := range n.Values {
if found := f.walk(node, value); found != nil {
return found
}
}
case *TagNode:
return f.walk(node, n.Value)
case *DocumentNode:
Expand Down
10 changes: 10 additions & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,16 @@ func (d *Decoder) nodeToValue(node ast.Node) (any, error) {
case *ast.NanNode:
return n.GetValue(), nil
case *ast.TagNode:
if n.Directive != nil {
v, err := d.nodeToValue(n.Value)
if err != nil {
return nil, err
}
if v == nil {
return "", nil
}
return fmt.Sprint(v), nil
}
switch token.ReservedTagKeyword(n.Start.Value) {
case token.TimestampTag:
t, _ := d.castToTime(n.Value)
Expand Down
131 changes: 119 additions & 12 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,28 @@ func ParseFile(filename string, mode Mode, opts ...Option) (*ast.File, error) {
return f, nil
}

type YAMLVersion string

const (
YAML10 YAMLVersion = "1.0"
YAML11 YAMLVersion = "1.1"
YAML12 YAMLVersion = "1.2"
YAML13 YAMLVersion = "1.3"
)

var yamlVersionMap = map[string]YAMLVersion{
"1.0": YAML10,
"1.1": YAML11,
"1.2": YAML12,
"1.3": YAML13,
}

type parser struct {
tokens []*Token
pathMap map[string]ast.Node
allowDuplicateMapKey bool
tokens []*Token
pathMap map[string]ast.Node
yamlVersion YAMLVersion
allowDuplicateMapKey bool
secondaryTagDirective *ast.DirectiveNode
}

func newParser(tokens token.Tokens, mode Mode, opts []Option) (*parser, error) {
Expand Down Expand Up @@ -122,6 +140,10 @@ func (p *parser) parseDocument(ctx *context, docGroup *TokenGroup) (*ast.Documen
if docGroup.Last().Type() == token.DocumentEndType {
end = docGroup.Last().RawToken()
tokens = tokens[:len(tokens)-1]
defer func() {
// clear yaml version value if DocumentEnd token (...) is specified.
p.yamlVersion = ""
}()
}

if len(tokens) == 0 {
Expand Down Expand Up @@ -162,6 +184,13 @@ func (p *parser) parseToken(ctx *context, tk *Token) (ast.Node, error) {
}
ctx.goNext()
return node, nil
case TokenGroupDirectiveName:
node, err := p.parseDirectiveName(ctx.withGroup(tk.Group))
if err != nil {
return nil, err
}
ctx.goNext()
return node, nil
case TokenGroupAnchor:
node, err := p.parseAnchor(ctx.withGroup(tk.Group), tk.Group)
if err != nil {
Expand Down Expand Up @@ -838,14 +867,26 @@ func (p *parser) parseTag(ctx *context) (*ast.TagNode, error) {
ctx.goNext()

comment := p.parseHeadComment(ctx)
value, err := p.parseTagValue(ctx, tagRawTk, ctx.currentToken())
if err != nil {
return nil, err

var tagValue ast.Node
if p.secondaryTagDirective != nil {
value, err := newStringNode(ctx, ctx.currentToken())
if err != nil {
return nil, err
}
tagValue = value
node.Directive = p.secondaryTagDirective
} else {
value, err := p.parseTagValue(ctx, tagRawTk, ctx.currentToken())
if err != nil {
return nil, err
}
tagValue = value
}
if err := setHeadComment(comment, value); err != nil {
if err := setHeadComment(comment, tagValue); err != nil {
return nil, err
}
node.Value = value
node.Value = tagValue
return node, nil
}

Expand Down Expand Up @@ -1046,16 +1087,82 @@ func (p *parser) parseSequenceValue(ctx *context, seqTk *Token) (ast.Node, error
}

func (p *parser) parseDirective(ctx *context, g *TokenGroup) (*ast.DirectiveNode, error) {
node, err := newDirectiveNode(ctx, g.First())
directiveNameGroup := g.First().Group
directive, err := p.parseDirectiveName(ctx.withGroup(directiveNameGroup))
if err != nil {
return nil, err
}
value, err := p.parseToken(ctx, g.Last())

switch directive.Name.String() {
case "YAML":
if len(g.Tokens) != 2 {
return nil, errors.ErrSyntax("unexpected format YAML directive", g.First().RawToken())
}
valueTk := g.Tokens[1]
valueRawTk := valueTk.RawToken()
value := valueRawTk.Value
ver, exists := yamlVersionMap[value]
if !exists {
return nil, errors.ErrSyntax(fmt.Sprintf("unknown YAML version %q", value), valueRawTk)
}
if p.yamlVersion != "" {
return nil, errors.ErrSyntax("YAML version has already been specified", valueRawTk)
}
p.yamlVersion = ver
versionNode, err := newStringNode(ctx, valueTk)
if err != nil {
return nil, err
}
directive.Values = append(directive.Values, versionNode)
case "TAG":
if len(g.Tokens) != 3 {
return nil, errors.ErrSyntax("unexpected format TAG directive", g.First().RawToken())
}
tagKey, err := newStringNode(ctx, g.Tokens[1])
if err != nil {
return nil, err
}
if tagKey.Value == "!!" {
p.secondaryTagDirective = directive
}
tagValue, err := newStringNode(ctx, g.Tokens[2])
if err != nil {
return nil, err
}
directive.Values = append(directive.Values, tagKey, tagValue)
default:
if len(g.Tokens) > 1 {
for _, tk := range g.Tokens[1:] {
value, err := newStringNode(ctx, tk)
if err != nil {
return nil, err
}
directive.Values = append(directive.Values, value)
}
}
}
return directive, nil
}

func (p *parser) parseDirectiveName(ctx *context) (*ast.DirectiveNode, error) {
directive, err := newDirectiveNode(ctx, ctx.currentToken())
if err != nil {
return nil, err
}
node.Value = value
return node, nil
ctx.goNext()
if ctx.isTokenNotFound() {
return nil, errors.ErrSyntax("could not find directive value", directive.GetToken())
}

directiveName, err := p.parseScalarValue(ctx, ctx.currentToken())
if err != nil {
return nil, err
}
if directiveName == nil {
return nil, errors.ErrSyntax("unexpected directive. directive name is not scalar value", ctx.currentToken().RawToken())
}
directive.Name = directiveName
return directive, nil
}

func (p *parser) parseComment(ctx *context) (ast.Node, error) {
Expand Down
Loading

0 comments on commit 438f2d0

Please sign in to comment.