feat(goccy): marshal back to goccy ast

This commit is contained in:
Lars Stegman 2025-05-15 14:13:39 +02:00
parent 99b8b9705b
commit 3800c3a4f2
No known key found for this signature in database
2 changed files with 194 additions and 95 deletions

View File

@ -207,82 +207,52 @@ func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingVa
return nil return nil
} }
func (o *CandidateNode) goccyMarshalChild(path string) (ast.Node, error) { func (o *CandidateNode) MarshalGoccyYAML() (ast.Node, error) {
marshaller := &goccyMarshaller{}
return marshaller.Marshal("$", o)
}
type goccyMarshaller struct {
currentToken *goccyToken.Token
}
func (m *goccyMarshaller) PushToken(t *goccyToken.Token) *goccyToken.Token {
if m.currentToken == nil {
m.currentToken = t
return m.currentToken
}
m.currentToken.Next = t
t.Prev = m.currentToken
m.currentToken = t
return m.currentToken
}
func (m *goccyMarshaller) Marshal(path string, o *CandidateNode) (ast.Node, error) {
var node ast.Node var node ast.Node
var err error
switch o.Kind { switch o.Kind {
case SequenceNode:
values := make([]ast.Node, len(o.Content))
for i, content := range o.Content {
childNode, err := content.goccyMarshalChild(path + "[" + strconv.Itoa(i) + "]")
if err != nil {
return nil, err
}
values[i] = childNode
}
seq := ast.Sequence(
goccyToken.SequenceStart(o.Value, &goccyToken.Position{}),
o.Style == FlowStyle,
)
seq.Values = values
seq.ValueHeadComments = []*ast.CommentGroupNode{ast.CommentGroup([]*goccyToken.Token{goccyToken.Comment(o.HeadComment, o.HeadComment, &goccyToken.Position{})})}
seq.FootComment = goccyMarshalFootComment(o.FootComment)
node = seq
case MappingNode:
values := make([]*ast.MappingValueNode, len(o.Content)/2)
for i := 0; i < len(o.Content); i += 2 {
key, err := o.Content[i].goccyMarshalMapKeyNode(path)
if err != nil {
return nil, err
}
value, err := o.Content[i+1].goccyMarshalChild(key.GetPath())
if err != nil {
return nil, err
}
childNode := ast.MappingValue(
goccyToken.MappingValue(&goccyToken.Position{}),
key,
value,
)
childNode.SetPath(key.GetPath())
if o.Content[i].FootComment != "" {
childNode.FootComment = goccyMarshalFootComment(o.Content[i].FootComment)
}
values[i/2] = childNode
}
mapping := ast.Mapping(goccyToken.MappingStart("", &goccyToken.Position{}), o.Style == FlowStyle, values...)
if o.FootComment != "" {
mapping.FootComment = goccyMarshalFootComment(o.FootComment)
}
node = mapping
case ScalarNode:
v, err := o.GetValueRep()
if err != nil {
return nil, err
}
node, err = o.goccyMarshalScalar(path, v)
if err != nil {
return nil, err
}
case AliasNode: case AliasNode:
value, err := o.Content[0].goccyMarshalChild(path) node, err = m.marshalAlias(path, o)
if err != nil { if err != nil {
return nil, err return nil, err
} }
case ScalarNode:
alias := ast.Alias(goccyToken.Alias(o.Value, &goccyToken.Position{})) node, err = m.marshalScalar(path, o)
alias.Value = value if err != nil {
node = alias return nil, err
}
case SequenceNode:
node, err = m.marshalSequence(path, o)
if err != nil {
return nil, err
}
case MappingNode:
node, err = m.marshalMapping(path, o)
} }
// TODO: Fix Tag and Anchor tokens
if o.Tag != "" && !strings.HasPrefix(o.Tag, "!!") { if o.Tag != "" && !strings.HasPrefix(o.Tag, "!!") {
tag := ast.Tag(goccyToken.Tag(o.Tag, o.Tag, &goccyToken.Position{})) tag := ast.Tag(goccyToken.Tag(o.Tag, o.Tag, &goccyToken.Position{}))
tag.Value = node tag.Value = node
@ -300,25 +270,136 @@ func (o *CandidateNode) goccyMarshalChild(path string) (ast.Node, error) {
return node, nil return node, nil
} }
func (o *CandidateNode) MarshalGoccyYAML() (ast.Node, error) { func (m *goccyMarshaller) marshalAlias(path string, o *CandidateNode) (ast.Node, error) {
return o.goccyMarshalChild("$") t := m.PushToken(goccyToken.Alias(o.Value, &goccyToken.Position{}))
} var value ast.Node
var err error
func goccyMarshalFootComment(comment string) *ast.CommentGroupNode { value, err = m.Marshal(path, o.Content[0])
return ast.CommentGroup([]*goccyToken.Token{goccyToken.Comment(comment, comment, &goccyToken.Position{})})
}
func (o *CandidateNode) goccyMarshalMapKeyNode(path string) (ast.MapKeyNode, error) {
if o.Kind != ScalarNode {
return nil, fmt.Errorf("cannot unmarshal non-scalar node to MapKeyNode")
}
v, err := o.GetValueRep()
if err != nil { if err != nil {
return nil, err return nil, err
} }
key, err := o.goccyMarshalScalar(path, v) alias := ast.Alias(t)
alias.Value = value
return alias, nil
}
func (m *goccyMarshaller) marshalSequence(path string, o *CandidateNode) (ast.Node, error) {
isFlowStyle := o.Style == FlowStyle
seq := ast.Sequence(
nil,
isFlowStyle,
)
if isFlowStyle {
seq.Start = m.PushToken(goccyToken.SequenceStart("[", &goccyToken.Position{}))
}
var err error
values := make([]ast.Node, len(o.Content))
for i, content := range o.Content {
m.PushToken(goccyToken.SequenceEntry("-", &goccyToken.Position{}))
values[i], err = m.Marshal(path+"["+strconv.Itoa(i)+"]", content)
if err != nil {
return nil, err
}
if isFlowStyle && i < len(o.Content)-1 {
m.PushToken(goccyToken.CollectEntry(",", &goccyToken.Position{}))
}
}
if isFlowStyle {
seq.End = m.PushToken(goccyToken.SequenceEnd("]", &goccyToken.Position{}))
}
seq.Values = values
seq.ValueHeadComments = []*ast.CommentGroupNode{ast.CommentGroup([]*goccyToken.Token{goccyToken.Comment(o.HeadComment, o.HeadComment, &goccyToken.Position{})})}
seq.FootComment = goccyMarshalFootComment(o.FootComment)
return seq, nil
}
func (m *goccyMarshaller) marshalMapping(path string, o *CandidateNode) (ast.Node, error) {
isFlowStyle := o.Style == FlowStyle
mapping := ast.Mapping(nil, isFlowStyle)
if isFlowStyle {
mapping.Start = m.PushToken(goccyToken.MappingStart("{", &goccyToken.Position{}))
}
values := make([]*ast.MappingValueNode, len(o.Content)/2)
var err error
for i := 0; i < len(o.Content); i += 2 {
var childNode *ast.MappingValueNode
childNode, err = m.marshalMappingValueNode(path, o.Content[i], o.Content[i+1])
if err != nil {
return nil, err
}
if o.Content[i].FootComment != "" {
childNode.FootComment = goccyMarshalFootComment(o.Content[i].FootComment)
}
values[i/2] = childNode
if isFlowStyle && i < len(o.Content)/2-1 {
m.PushToken(goccyToken.CollectEntry(",", &goccyToken.Position{}))
}
}
mapping.Values = values
if !isFlowStyle && len(values) > 0 {
mapping.Start = values[0].Start
}
if isFlowStyle {
mapping.End = m.PushToken(goccyToken.MappingEnd("}", &goccyToken.Position{}))
}
if o.FootComment != "" {
mapping.FootComment = goccyMarshalFootComment(o.FootComment)
}
return mapping, nil
}
func (m *goccyMarshaller) marshalMappingValueNode(path string, keyNode, valueNode *CandidateNode) (*ast.MappingValueNode, error) {
var key ast.MapKeyNode
var err error
key, err = m.marshalMapKeyNode(path, keyNode)
if err != nil {
return nil, err
}
separatorToken := m.PushToken(goccyToken.MappingValue(&goccyToken.Position{}))
value, err := m.Marshal(key.GetPath(), valueNode)
if err != nil {
return nil, err
}
childNode := ast.MappingValue(
separatorToken,
key,
value,
)
childNode.SetPath(key.GetPath())
return childNode, nil
}
func goccyMarshalFootComment(comment string) *ast.CommentGroupNode {
// TODO: Fix Tokens
return ast.CommentGroup([]*goccyToken.Token{goccyToken.Comment(comment, comment, &goccyToken.Position{})})
}
func (m *goccyMarshaller) marshalMapKeyNode(path string, o *CandidateNode) (ast.MapKeyNode, error) {
if o.Kind != ScalarNode {
return nil, fmt.Errorf("cannot unmarshal non-scalar node to MapKeyNode")
}
key, err := m.marshalScalar(path, o)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -327,30 +408,35 @@ func (o *CandidateNode) goccyMarshalMapKeyNode(path string) (ast.MapKeyNode, err
return key, nil return key, nil
} }
func (o *CandidateNode) goccyMarshalScalar(path string, v any) (ast.ScalarNode, error) { func (m *goccyMarshaller) marshalScalar(path string, o *CandidateNode) (ast.ScalarNode, error) {
t := goccyToken.Literal(o.Value, o.Value, &goccyToken.Position{}) v, err := o.GetValueRep()
if err != nil {
return nil, err
}
scalarToken := m.PushToken(goccyToken.Literal(o.Value, o.Value, &goccyToken.Position{}))
if v == nil { if v == nil {
n := ast.Null(t) n := ast.Null(scalarToken)
n.SetPath(path) n.SetPath(path)
return n, nil return n, nil
} }
switch av := v.(type) { switch av := v.(type) {
case float64: case float64:
n := ast.Float(t) n := ast.Float(scalarToken)
n.Value = av n.Value = av
return n, nil return n, nil
case int64: case int64:
n := ast.Integer(t) n := ast.Integer(scalarToken)
n.Value = av n.Value = av
return n, nil return n, nil
case bool: case bool:
n := ast.Bool(t) n := ast.Bool(scalarToken)
n.Value = av n.Value = av
return n, nil return n, nil
case string: case string:
n := ast.String(t) n := ast.String(scalarToken)
n.SetPath(path) n.SetPath(path)
return n, nil return n, nil
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/goccy/go-yaml/parser"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -17,10 +18,15 @@ a:
s: s:
- 1 - 1
- 2 - 2
t: [5, five]
f: [6, {y: true}]
` `
goccyAst, err := parser.ParseBytes([]byte(input), parser.ParseComments)
fmt.Println(goccyAst)
var yamlNode yaml.Node var yamlNode yaml.Node
err := yaml.Unmarshal([]byte(input), &yamlNode) err = yaml.Unmarshal([]byte(input), &yamlNode)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
return return
@ -39,5 +45,12 @@ s:
return return
} }
parsed, err := parser.ParseBytes([]byte(input), parser.ParseComments)
if err != nil {
t.Error(err)
return
}
fmt.Println(parsed)
fmt.Println(goccyNode) fmt.Println(goccyNode)
} }