From 1bae751a45273e9ddaa1550bce40e0a50c287dc5 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Thu, 25 Mar 2021 19:56:02 -0400 Subject: [PATCH] Linear array storage for AST --- internal/ast/ast.go | 277 +++++++++++++----------------------- internal/ast/builder.go | 58 ++++++++ internal/ast/kind.go | 69 +++++++++ parser.go | 308 +++++++++++++++++++++++----------------- parser_test.go | 184 +++++++++++++++++------- unmarshaler.go | 74 ++++++---- unmarshaler_test.go | 149 +++++++++---------- 7 files changed, 659 insertions(+), 460 deletions(-) create mode 100644 internal/ast/builder.go create mode 100644 internal/ast/kind.go diff --git a/internal/ast/ast.go b/internal/ast/ast.go index e927dd0..d8d1843 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -2,182 +2,103 @@ package ast import ( "fmt" - "strings" ) -type Kind int - -const ( - // meta - Invalid Kind = iota - Comment - Key - - // top level structures - Table - ArrayTable - KeyValue - - // containers values - Array - InlineTable - - // values - String - Bool - Float - Integer - LocalDate - LocalDateTime - DateTime - Time -) - -func (k Kind) String() string { - switch k { - case Invalid: - return "Invalid" - case Comment: - return "Comment" - case Key: - return "Key" - case Table: - return "Table" - case ArrayTable: - return "ArrayTable" - case KeyValue: - return "KeyValue" - case Array: - return "Array" - case InlineTable: - return "InlineTable" - case String: - return "String" - case Bool: - return "Bool" - case Float: - return "Float" - case Integer: - return "Integer" - case LocalDate: - return "LocalDate" - case LocalDateTime: - return "LocalDateTime" - case DateTime: - return "DateTime" - case Time: - return "Time" - } - panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) +// Iterator starts uninitialized, you need to call Next() first. +// +// For example: +// +// it := n.Children() +// for it.Next() { +// it.Node() +// } +type Iterator interface { + // Next moves the iterator forward and returns true if points to a node, false + // otherwise. + Next() bool + // Node returns a copy of the node pointed at by the iterator. + Node() Node } -type Root []Node - -// Dot returns a dot representation of the AST for debugging. -func (r Root) Sdot() string { - type edge struct { - from int - childIdx int - to int - } - - var nodes []Node - var edges []edge // indexes into nodes - - nodes = append(nodes, Node{ - Kind: Invalid, - Data: []byte(`ROOT`), - Children: r, - }) - - var processNode func(int, int, *Node) - processNode = func(parentIdx int, childIdx int, node *Node) { - idx := len(nodes) - nodes = append(nodes, *node) - edges = append(edges, edge{ - from: parentIdx, - childIdx: childIdx, - to: idx, - }) - - for i, c := range node.Children { - processNode(idx, i, &c) - } - } - - for i, n := range r { - processNode(0, i, &n) - } - - var b strings.Builder - - b.WriteString("digraph tree {\n") - b.WriteString("\tnode [shape=record];\n") - - for i, node := range nodes { - label := "" - attrs := map[string]string{} - - if i == 0 { - var ports []string - for i := 0; i < len(node.Children); i++ { - ports = append(ports, fmt.Sprintf(" %d", i, i)) - } - joinedPorts := strings.Join(ports, "|") - label = fmt.Sprintf("{ROOT|{%s}}", joinedPorts) - } else { - fields := []string{node.Kind.String()} - if len(node.Data) > 0 { - fields = append(fields, string(node.Data)) - } - - var ports []string - for i := 0; i < len(node.Children); i++ { - ports = append(ports, fmt.Sprintf(" %d", i, i)) - } - joinedPorts := strings.Join(ports, "|") - - joinedFields := strings.Join(fields, "|") - label = fmt.Sprintf("{{%s}", joinedFields) - if len(ports) > 0 { - label += fmt.Sprintf("|{%s}", joinedPorts) - } - label += "}" - if node.Kind == Invalid { - attrs["style"] = "filled" - attrs["fillcolor"] = "red" - } - } - - _, _ = fmt.Fprintf(&b, "\tnode%d [label=\"%s\"", i, label) - for k, v := range attrs { - _, _ = fmt.Fprintf(&b, ", %s=\"%s\"", k, v) - } - _, _ = fmt.Fprintf(&b, "];\n") - } - - b.WriteString("\n") - - for _, e := range edges { - _, _ = fmt.Fprintf(&b, "\tnode%d:f%d -> node%d;\n", e.from, e.childIdx, e.to) - } - - b.WriteString("}") - - return b.String() +type chainIterator struct { + started bool + node Node } +func (c *chainIterator) Next() bool { + if !c.started { + c.started = true + } else if c.node.Valid() { + c.node = c.node.Next() + } + return c.node.Valid() +} + +func (c *chainIterator) Node() Node { + return c.node +} + +type Root struct { + nodes []Node +} + +func (r *Root) Iterator() Iterator { + it := &chainIterator{} + if len(r.nodes) > 0 { + it.node = r.nodes[0] + } + return it +} + +func (r *Root) Reset() { + r.nodes = r.nodes[:0] +} + +func (r *Root) at(idx int) Node { + // TODO: unsafe to point to the node directly + return r.nodes[idx] +} + +// Arrays have one child per element in the array. +// InlineTables have one child per key-value pair in the table. +// KeyValues have at least two children. The first one is the value. The +// rest make a potentially dotted key. +// Table and Array table have one child per element of the key they +// represent (same as KeyValue, but without the last node being the value). +// children []Node type Node struct { Kind Kind Data []byte // Raw bytes from the input - // Arrays have one child per element in the array. - // InlineTables have one child per key-value pair in the table. - // KeyValues have at least two children. The last one is the value. The - // rest make a potentially dotted key. - // Table and Array table have one child per element of the key they - // represent (same as KeyValue, but without the last node being the value). - Children []Node + // next idx (in the root array). 0 if last of the collection. + next int + // child idx (in the root array). 0 if no child. + child int + // pointer to the root array + root *Root +} + +// Next returns a copy of the next node, or an invalid Node if there is no +// next node. +func (n Node) Next() Node { + if n.next <= 0 { + return NoNode + } + return n.root.at(n.next) +} + +// Child returns a copy of the first child node of this node. Other children +// can be accessed calling Next on the first child. +// Returns an invalid Node if there is none. +func (n Node) Child() Node { + if n.child <= 0 { + return NoNode + } + return n.root.at(n.child) +} + +func (n Node) Valid() bool { + return n.Kind != Invalid } var NoNode = Node{} @@ -186,15 +107,16 @@ var NoNode = Node{} // otherwise. // They are guaranteed to be all be of the Kind Key. A simple key would return // just one element. -func (n *Node) Key() []Node { +func (n *Node) Key() Iterator { switch n.Kind { case KeyValue: - if len(n.Children) < 2 { - panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) + value := n.Child() + if !value.Valid() { + panic(fmt.Errorf("KeyValue should have at least two children")) } - return n.Children[:len(n.Children)-1] + return &chainIterator{node: value.Next()} case Table, ArrayTable: - return n.Children + return &chainIterator{node: n.Child()} default: panic(fmt.Errorf("Key() is not supported on a %s", n.Kind)) } @@ -203,15 +125,16 @@ func (n *Node) Key() []Node { // Value returns a pointer to the value node of a KeyValue. // Guaranteed to be non-nil. // Panics if not called on a KeyValue node, or if the Children are malformed. -func (n *Node) Value() *Node { +func (n Node) Value() Node { assertKind(KeyValue, n) - if len(n.Children) < 2 { - panic(fmt.Errorf("KeyValue should have at least two children, not %d", len(n.Children))) - } - return &n.Children[len(n.Children)-1] + return n.Child() } -func assertKind(k Kind, n *Node) { +func (n Node) Children() Iterator { + return &chainIterator{node: n.Child()} +} + +func assertKind(k Kind, n Node) { if n.Kind != k { panic(fmt.Errorf("method was expecting a %s, not a %s", k, n.Kind)) } diff --git a/internal/ast/builder.go b/internal/ast/builder.go new file mode 100644 index 0000000..a6868cd --- /dev/null +++ b/internal/ast/builder.go @@ -0,0 +1,58 @@ +package ast + +type Builder struct { + nodes []Node + lastIdx int +} + +type Reference struct { + idx int + set bool +} + +func (r Reference) Valid() bool { + return r.set +} + +func (b *Builder) Finish() *Root { + r := &Root{ + nodes: b.nodes, + } + b.nodes = nil + + for i := range r.nodes { + r.nodes[i].root = r + } + + return r +} + +func (b *Builder) Push(n Node) Reference { + b.lastIdx = len(b.nodes) + b.nodes = append(b.nodes, n) + return Reference{ + idx: b.lastIdx, + set: true, + } +} + +func (b *Builder) PushAndChain(n Node) Reference { + newIdx := len(b.nodes) + b.nodes = append(b.nodes, n) + if b.lastIdx >= 0 { + b.nodes[b.lastIdx].next = newIdx + } + b.lastIdx = newIdx + return Reference{ + idx: b.lastIdx, + set: true, + } +} + +func (b *Builder) AttachChild(parent Reference, child Reference) { + b.nodes[parent.idx].child = child.idx +} + +func (b *Builder) Chain(from Reference, to Reference) { + b.nodes[from.idx].next = to.idx +} diff --git a/internal/ast/kind.go b/internal/ast/kind.go new file mode 100644 index 0000000..bcf6c91 --- /dev/null +++ b/internal/ast/kind.go @@ -0,0 +1,69 @@ +package ast + +import "fmt" + +type Kind int + +const ( + // meta + Invalid Kind = iota + Comment + Key + + // top level structures + Table + ArrayTable + KeyValue + + // containers values + Array + InlineTable + + // values + String + Bool + Float + Integer + LocalDate + LocalDateTime + DateTime + Time +) + +func (k Kind) String() string { + switch k { + case Invalid: + return "Invalid" + case Comment: + return "Comment" + case Key: + return "Key" + case Table: + return "Table" + case ArrayTable: + return "ArrayTable" + case KeyValue: + return "KeyValue" + case Array: + return "Array" + case InlineTable: + return "InlineTable" + case String: + return "String" + case Bool: + return "Bool" + case Float: + return "Float" + case Integer: + return "Integer" + case LocalDate: + return "LocalDate" + case LocalDateTime: + return "LocalDateTime" + case DateTime: + return "DateTime" + case Time: + return "Time" + } + panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) +} diff --git a/parser.go b/parser.go index ce45e55..f2ca2df 100644 --- a/parser.go +++ b/parser.go @@ -10,11 +10,11 @@ import ( ) type parser struct { - tree ast.Root + builder ast.Builder } func (p *parser) parse(b []byte) error { - b, err := p.parseExpression(b) + last, b, err := p.parseExpression(b) if err != nil { return err } @@ -24,10 +24,15 @@ func (p *parser) parse(b []byte) error { return err } - b, err = p.parseExpression(b) + var next ast.Reference + next, b, err = p.parseExpression(b) if err != nil { return err } + if next.Valid() { + p.builder.Chain(last, next) + last = next + } } return nil } @@ -43,49 +48,48 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) { return nil, fmt.Errorf("expected newline but got %#U", b[0]) } -func (p *parser) parseExpression(b []byte) ([]byte, error) { +func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { //expression = ws [ comment ] //expression =/ ws keyval ws [ comment ] //expression =/ ws table ws [ comment ] + var ref ast.Reference + b = p.parseWhitespace(b) if len(b) == 0 { - return b, nil + return ref, b, nil } if b[0] == '#' { _, rest, err := scanComment(b) - return rest, err + return ref, rest, err } if b[0] == '\n' || b[0] == '\r' { - return b, nil + return ref, b, nil } var err error - var node ast.Node if b[0] == '[' { - node, b, err = p.parseTable(b) + ref, b, err = p.parseTable(b) } else { - node, b, err = p.parseKeyval(b) + ref, b, err = p.parseKeyval(b) } if err != nil { - return nil, err + return ref, nil, err } - p.tree = append(p.tree, node) - b = p.parseWhitespace(b) if len(b) > 0 && b[0] == '#' { _, rest, err := scanComment(b) - return rest, err + return ref, rest, err } - return b, nil + return ref, b, nil } -func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) { //table = std-table / array-table if len(b) > 1 && b[1] == '[' { return p.parseArrayTable(b) @@ -93,90 +97,95 @@ func (p *parser) parseTable(b []byte) (ast.Node, []byte, error) { return p.parseStdTable(b) } -func (p *parser) parseArrayTable(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) { //array-table = array-table-open key array-table-close //array-table-open = %x5B.5B ws ; [[ Double left square bracket //array-table-close = ws %x5D.5D ; ]] Double right square bracket - node := ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.ArrayTable, - } + }) b = b[2:] b = p.parseWhitespace(b) k, b, err := p.parseKey(b) if err != nil { - return node, nil, err + return ref, nil, err } - node.Children = k + p.builder.AttachChild(ref, k) b = p.parseWhitespace(b) b, err = expect(']', b) if err != nil { - return node, nil, err + return ref, nil, err } b, err = expect(']', b) - return node, b, err + return ref, b, err } -func (p *parser) parseStdTable(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { //std-table = std-table-open key std-table-close //std-table-open = %x5B ws ; [ Left square bracket //std-table-close = ws %x5D ; ] Right square bracket - node := ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.Table, - } + }) b = b[1:] b = p.parseWhitespace(b) key, b, err := p.parseKey(b) if err != nil { - return ast.NoNode, nil, err + return ref, nil, err } - node.Children = key + + p.builder.AttachChild(ref, key) + b = p.parseWhitespace(b) b, err = expect(']', b) - return node, b, err + return ref, b, err } -func (p *parser) parseKeyval(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { //keyval = key keyval-sep val - node := ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.KeyValue, - } + }) key, b, err := p.parseKey(b) if err != nil { - return ast.NoNode, nil, err + return ast.Reference{}, nil, err } - node.Children = append(node.Children, key...) //keyval-sep = ws %x3D ws ; = b = p.parseWhitespace(b) b, err = expect('=', b) if err != nil { - return ast.NoNode, nil, err + return ast.Reference{}, nil, err } b = p.parseWhitespace(b) - valNode, b, err := p.parseVal(b) - if err == nil { - node.Children = append(node.Children, valNode) + valRef, b, err := p.parseVal(b) + if err != nil { + return ref, b, err } - return node, b, err + p.builder.Chain(valRef, key) + p.builder.AttachChild(ref, valRef) + + return ref, b, err } -func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { +func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { // val = string / boolean / array / inline-table / date-time / float / integer + var ref ast.Reference + if len(b) == 0 { - return ast.NoNode, nil, fmt.Errorf("expected value, not eof") + return ref, nil, fmt.Errorf("expected value, not eof") } - node := ast.Node{} var err error c := b[0] @@ -189,10 +198,12 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { v, b, err = p.parseBasicString(b) } if err == nil { - node.Kind = ast.String - node.Data = v + ref = p.builder.Push(ast.Node{ + Kind: ast.String, + Data: v, + }) } - return node, b, err + return ref, b, err case '\'': var v []byte if scanFollowsMultilineLiteralStringDelimiter(b) { @@ -201,35 +212,36 @@ func (p *parser) parseVal(b []byte) (ast.Node, []byte, error) { v, b, err = p.parseLiteralString(b) } if err == nil { - node.Kind = ast.String - node.Data = v + ref = p.builder.Push(ast.Node{ + Kind: ast.String, + Data: v, + }) } - return node, b, err + return ref, b, err case 't': if !scanFollowsTrue(b) { - return node, nil, fmt.Errorf("expected 'true'") + return ref, nil, fmt.Errorf("expected 'true'") } - node.Kind = ast.Bool - node.Data = b[:4] - return node, b[4:], nil + ref = p.builder.Push(ast.Node{ + Kind: ast.Bool, + Data: b[:4], + }) + return ref, b[4:], nil case 'f': if !scanFollowsFalse(b) { - return node, nil, fmt.Errorf("expected 'false'") + return ast.Reference{}, nil, fmt.Errorf("expected 'false'") } - node.Kind = ast.Bool - node.Data = b[:5] - return node, b[5:], nil + ref = p.builder.Push(ast.Node{ + Kind: ast.Bool, + Data: b[:5], + }) + return ref, b[5:], nil case '[': - node.Kind = ast.Array - b, err := p.parseValArray(&node, b) - return node, b, err + return p.parseValArray(b) case '{': - node.Kind = ast.InlineTable - b, err := p.parseInlineTable(&node, b) - return node, b, err + return p.parseInlineTable(b) default: - b, err = p.parseIntOrFloatOrDateTime(&node, b) - return node, b, err + return p.parseIntOrFloatOrDateTime(b) } } @@ -241,16 +253,22 @@ func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, error) { return v[1 : len(v)-1], rest, nil } -func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { //inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close //inline-table-open = %x7B ws ; { //inline-table-close = ws %x7D ; } //inline-table-sep = ws %x2C ws ; , Comma //inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] - b = b[1:] + parent := p.builder.Push(ast.Node{ + Kind: ast.InlineTable, + }) first := true + var child ast.Reference + + b = b[1:] + var err error for len(b) > 0 { b = p.parseWhitespace(b) @@ -261,24 +279,32 @@ func (p *parser) parseInlineTable(node *ast.Node, b []byte) ([]byte, error) { if !first { b, err = expect(',', b) if err != nil { - return nil, err + return parent, nil, err } b = p.parseWhitespace(b) } - var kv ast.Node + var kv ast.Reference kv, b, err = p.parseKeyval(b) if err != nil { - return nil, err + return parent, nil, err } - node.Children = append(node.Children, kv) + + if first { + p.builder.AttachChild(parent, kv) + first = false + } else { + p.builder.Chain(child, kv) + } + child = kv first = false } - return expect('}', b) + rest, err := expect('}', b) + return parent, rest, err } -func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { //array = array-open [ array-values ] ws-comment-newline array-close //array-open = %x5B ; [ //array-close = %x5D ; ] @@ -289,16 +315,22 @@ func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { b = b[1:] + parent := p.builder.Push(ast.Node{ + Kind: ast.Array, + }) + first := true + var lastChild ast.Reference + var err error for len(b) > 0 { b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { - return nil, err + return parent, nil, err } if len(b) == 0 { - return nil, unexpectedCharacter{b: b} + return parent, nil, unexpectedCharacter{b: b} } if b[0] == ']' { @@ -306,29 +338,38 @@ func (p *parser) parseValArray(node *ast.Node, b []byte) ([]byte, error) { } if b[0] == ',' { if first { - return nil, fmt.Errorf("array cannot start with comma") + return parent, nil, fmt.Errorf("array cannot start with comma") } b = b[1:] b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { - return nil, err + return parent, nil, err } } - var valueNode ast.Node - valueNode, b, err = p.parseVal(b) + var valueRef ast.Reference + valueRef, b, err = p.parseVal(b) if err != nil { - return nil, err + return parent, nil, err } - node.Children = append(node.Children, valueNode) + + if first { + p.builder.AttachChild(parent, valueRef) + first = false + } else { + p.builder.Chain(lastChild, valueRef) + } + lastChild = valueRef + b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { - return nil, err + return parent, nil, err } first = false } - return expect(']', b) + rest, err := expect(']', b) + return parent, rest, err } func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { @@ -454,7 +495,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, error) { return builder.Bytes(), rest, nil } -func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { +func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { //key = simple-key / dotted-key //simple-key = quoted-key / unquoted-key // @@ -464,14 +505,12 @@ func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { // //dot-sep = ws %x2E ws ; . Period - var nodes []ast.Node - key, b, err := p.parseSimpleKey(b) if err != nil { - return nodes, nil, err + return ast.Reference{}, nil, err } - nodes = append(nodes, ast.Node{ + ref := p.builder.Push(ast.Node{ Kind: ast.Key, Data: key, }) @@ -481,14 +520,14 @@ func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { if len(b) > 0 && b[0] == '.' { b, err = expect('.', b) if err != nil { - return nodes, nil, err + return ref, nil, err } b = p.parseWhitespace(b) key, b, err = p.parseSimpleKey(b) if err != nil { - return nodes, nil, err + return ref, nil, err } - nodes = append(nodes, ast.Node{ + p.builder.PushAndChain(ast.Node{ Kind: ast.Key, Data: key, }) @@ -497,7 +536,7 @@ func (p *parser) parseKey(b []byte) ([]ast.Node, []byte, error) { } } - return nodes, b, nil + return ref, b, nil } func (p *parser) parseSimpleKey(b []byte) (key, rest []byte, err error) { @@ -609,28 +648,30 @@ func (p *parser) parseWhitespace(b []byte) []byte { return rest } -func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, error) { switch b[0] { case 'i': if !scanFollowsInf(b) { - return nil, fmt.Errorf("expected 'inf'") + return ast.Reference{}, nil, fmt.Errorf("expected 'inf'") } - node.Kind = ast.Float - node.Data = b[:3] - return b[3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:3], + }), b[3:], nil case 'n': if !scanFollowsNan(b) { - return nil, fmt.Errorf("expected 'nan'") + return ast.Reference{}, nil, fmt.Errorf("expected 'nan'") } - node.Kind = ast.Float - node.Data = b[:3] - return b[3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:3], + }), b[3:], nil case '+', '-': - return p.scanIntOrFloat(node, b) + return p.scanIntOrFloat(b) } if len(b) < 3 { - return p.scanIntOrFloat(node, b) + return p.scanIntOrFloat(b) } s := 5 if len(b) < s { @@ -641,10 +682,10 @@ func (p *parser) parseIntOrFloatOrDateTime(node *ast.Node, b []byte) ([]byte, er continue } if idx == 2 && c == ':' || (idx == 4 && c == '-') { - return p.scanDateTime(node, b) + return p.scanDateTime(b) } } - return p.scanIntOrFloat(node, b) + return p.scanIntOrFloat(b) } func digitsToInt(b []byte) int { @@ -656,7 +697,7 @@ func digitsToInt(b []byte) int { return x } -func (p *parser) scanDateTime(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { // scans for contiguous characters in [0-9T:Z.+-], and up to one space if // followed by a digit. @@ -686,22 +727,25 @@ func (p *parser) scanDateTime(node *ast.Node, b []byte) ([]byte, error) { } } + var kind ast.Kind + if hasTime { if hasTz { - node.Kind = ast.DateTime + kind = ast.DateTime } else { - node.Kind = ast.LocalDateTime + kind = ast.LocalDateTime } } else { if hasTz { - return nil, fmt.Errorf("possible DateTime cannot have a timezone but no time component") + return ast.Reference{}, nil, fmt.Errorf("possible DateTime cannot have a timezone but no time component") } - node.Kind = ast.LocalDate + kind = ast.LocalDate } - node.Data = b[:i] - - return b[i:], nil + return p.builder.Push(ast.Node{ + Kind: kind, + Data: b[:i], + }), b[i:], nil } func (p *parser) parseDateTime(b []byte) ([]byte, error) { @@ -964,7 +1008,7 @@ func (p *parser) parseTime(b []byte) ([]byte, error) { return b[idx:], nil } -func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { +func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { i := 0 if len(b) > 2 && b[0] == '0' { @@ -989,9 +1033,10 @@ func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { } } - node.Kind = ast.Integer - node.Data = b[:i] - return b[i:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Integer, + Data: b[:i], + }), b[i:], nil } isFloat := false @@ -1010,31 +1055,36 @@ func (p *parser) scanIntOrFloat(node *ast.Node, b []byte) ([]byte, error) { if c == 'i' { if scanFollowsInf(b[i:]) { - node.Kind = ast.Float - node.Data = b[:i+3] - return b[i+3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:i+3], + }), b[i+3:], nil } - return nil, fmt.Errorf("unexpected character i while scanning for a number") + return ast.Reference{}, nil, fmt.Errorf("unexpected character i while scanning for a number") } if c == 'n' { if scanFollowsNan(b[i:]) { - node.Kind = ast.Float - node.Data = b[:i+3] - return b[i+3:], nil + return p.builder.Push(ast.Node{ + Kind: ast.Float, + Data: b[:i+3], + }), b[i+3:], nil } - return nil, fmt.Errorf("unexpected character n while scanning for a number") + return ast.Reference{}, nil, fmt.Errorf("unexpected character n while scanning for a number") } break } + kind := ast.Integer + if isFloat { - node.Kind = ast.Float - } else { - node.Kind = ast.Integer + kind = ast.Float } - node.Data = b[:i] - return b[i:], nil + + return p.builder.Push(ast.Node{ + Kind: kind, + Data: b[:i], + }), b[i:], nil } func isDigit(r byte) bool { diff --git a/parser_test.go b/parser_test.go index 2182653..1f55047 100644 --- a/parser_test.go +++ b/parser_test.go @@ -125,44 +125,128 @@ func TestParser_AST_Numbers(t *testing.T) { } else { require.NoError(t, err) - expected := ast.Root{ - ast.Node{ + expected := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`A`)}, + Children: []astNode{ {Kind: e.kind, Data: []byte(e.input)}, + {Kind: ast.Key, Data: []byte(`A`)}, }, }, } - require.Equal(t, expected, p.tree) + compareAST(t, expected, p.builder.Finish()) } }) } } +type astRoot []astNode +type astNode struct { + Kind ast.Kind + Data []byte + Children []astNode +} + +func compareAST(t *testing.T, expected astRoot, actual *ast.Root) { + it := actual.Iterator() + compareIterator(t, expected, it) +} + +func compareIterator(t *testing.T, expected []astNode, actual ast.Iterator) { + idx := 0 + + for actual.Next() { + n := actual.Node() + + if idx >= len(expected) { + t.Fatal("extra child in actual tree") + } + e := expected[idx] + + require.Equal(t, e.Kind, n.Kind) + require.Equal(t, e.Data, n.Data) + + compareIterator(t, e.Children, n.Children()) + + idx++ + } + + if idx < len(expected) { + t.Fatal("missing children in actual", "idx =", idx, "expected =", len(expected)) + } +} + +func (r astRoot) toOrig() *ast.Root { + builder := &ast.Builder{} + + var last ast.Reference + + for i, n := range r { + ref := builder.Push(ast.Node{ + Kind: n.Kind, + Data: n.Data, + }) + + if i > 0 { + builder.Chain(last, ref) + } + last = ref + + if len(n.Children) > 0 { + c := childrenToOrig(builder, n.Children) + builder.AttachChild(ref, c) + } + } + + return builder.Finish() +} + +func childrenToOrig(b *ast.Builder, nodes []astNode) ast.Reference { + var first ast.Reference + var last ast.Reference + for i, n := range nodes { + ref := b.Push(ast.Node{ + Kind: n.Kind, + Data: n.Data, + }) + if i == 0 { + first = ref + } else { + b.Chain(last, ref) + } + last = ref + + if len(n.Children) > 0 { + c := childrenToOrig(b, n.Children) + b.AttachChild(ref, c) + } + } + return first +} + func TestParser_AST(t *testing.T) { examples := []struct { desc string input string - ast ast.Root + ast astRoot err bool }{ { desc: "simple string assignment", input: `A = "hello"`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -170,18 +254,18 @@ func TestParser_AST(t *testing.T) { { desc: "simple bool assignment", input: `A = true`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.Bool, Data: []byte(`true`), }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -189,24 +273,20 @@ func TestParser_AST(t *testing.T) { { desc: "array of strings", input: `A = ["hello", ["world", "again"]]`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`world`), @@ -219,6 +299,10 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -226,17 +310,13 @@ func TestParser_AST(t *testing.T) { { desc: "array of arrays of strings", input: `A = ["hello", "world"]`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), @@ -247,6 +327,10 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`A`), + }, }, }, }, @@ -254,33 +338,33 @@ func TestParser_AST(t *testing.T) { { desc: "inline table", input: `name = { first = "Tom", last = "Preston-Werner" }`, - ast: ast.Root{ - ast.Node{ + ast: astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`name`), - }, + Children: []astNode{ { Kind: ast.InlineTable, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`first`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Tom`)}, + {Kind: ast.Key, Data: []byte(`first`)}, }, }, { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`last`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + {Kind: ast.Key, Data: []byte(`last`)}, }, }, }, }, + { + Kind: ast.Key, + Data: []byte(`name`), + }, }, }, }, @@ -295,7 +379,7 @@ func TestParser_AST(t *testing.T) { require.Error(t, err) } else { require.NoError(t, err) - require.Equal(t, e.ast, p.tree) + compareAST(t, e.ast, p.builder.Finish()) } }) } diff --git a/unmarshaler.go b/unmarshaler.go index d8b31d6..14c66b4 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -17,7 +17,7 @@ func Unmarshal(data []byte, v interface{}) error { } d := decoder{} - return d.fromAst(p.tree, v) + return d.fromAst(p.builder.Finish(), v) } type decoder struct { @@ -41,7 +41,7 @@ func (d *decoder) arrayIndex(append bool, v reflect.Value) int { return idx } -func (d *decoder) fromAst(tree ast.Root, v interface{}) error { +func (d *decoder) fromAst(tree *ast.Root, v interface{}) error { r := reflect.ValueOf(v) if r.Kind() != reflect.Ptr { return fmt.Errorf("need to target a pointer, not %s", r.Kind()) @@ -54,14 +54,17 @@ func (d *decoder) fromAst(tree ast.Root, v interface{}) error { var skipUntilTable bool var root target = valueTarget(r.Elem()) current := root - for _, node := range tree { + + it := tree.Iterator() + for it.Next() { + node := it.Node() var found bool switch node.Kind { case ast.KeyValue: if skipUntilTable { continue } - err = d.unmarshalKeyValue(current, &node) + err = d.unmarshalKeyValue(current, node) found = true case ast.Table: current, found, err = d.scopeWithKey(root, node.Key()) @@ -91,10 +94,12 @@ func (d *decoder) fromAst(tree ast.Root, v interface{}) error { // // When encountering slices, it should always use its last element, and error // if the slice does not have any. -func (d *decoder) scopeWithKey(x target, key []ast.Node) (target, bool, error) { +func (d *decoder) scopeWithKey(x target, key ast.Iterator) (target, bool, error) { var err error found := true - for _, n := range key { + + for key.Next() { + n := key.Node() x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return nil, found, err @@ -108,18 +113,21 @@ func (d *decoder) scopeWithKey(x target, key []ast.Node) (target, bool, error) { // // It is the same as scopeWithKey, but when scoping the last part of the key // it creates a new element in the array instead of using the last one. -func (d *decoder) scopeWithArrayTable(x target, key []ast.Node) (target, bool, error) { +func (d *decoder) scopeWithArrayTable(x target, key ast.Iterator) (target, bool, error) { var err error found := true - if len(key) > 1 { - for _, n := range key[:len(key)-1] { - x, found, err = d.scopeTableTarget(false, x, string(n.Data)) - if err != nil || !found { - return nil, found, err - } + for key.Next() { + n := key.Node() + if !n.Next().Valid() { // want to stop at one before last + break + } + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) + if err != nil || !found { + return nil, found, err } } - x, found, err = d.scopeTableTarget(false, x, string(key[len(key)-1].Data)) + n := key.Node() + x, found, err = d.scopeTableTarget(false, x, string(n.Data)) if err != nil || !found { return x, found, err } @@ -152,7 +160,7 @@ func (d *decoder) scopeWithArrayTable(x target, key []ast.Node) (target, bool, e return x, found, err } -func (d *decoder) unmarshalKeyValue(x target, node *ast.Node) error { +func (d *decoder) unmarshalKeyValue(x target, node ast.Node) error { assertNode(ast.KeyValue, node) x, found, err := d.scopeWithKey(x, node.Key()) @@ -170,7 +178,7 @@ func (d *decoder) unmarshalKeyValue(x target, node *ast.Node) error { var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() -func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { +func tryTextUnmarshaler(x target, node ast.Node) (bool, error) { v := x.get() if v.Kind() != reflect.Struct { @@ -185,7 +193,7 @@ func tryTextUnmarshaler(x target, node *ast.Node) (bool, error) { return false, nil } -func (d *decoder) unmarshalValue(x target, node *ast.Node) error { +func (d *decoder) unmarshalValue(x target, node ast.Node) error { v := x.get() if v.Kind() == reflect.Ptr { if !v.Elem().IsValid() { @@ -225,7 +233,7 @@ func (d *decoder) unmarshalValue(x target, node *ast.Node) error { } } -func unmarshalLocalDateTime(x target, node *ast.Node) error { +func unmarshalLocalDateTime(x target, node ast.Node) error { assertNode(ast.LocalDateTime, node) v, rest, err := parseLocalDateTime(node.Data) if err != nil { @@ -237,7 +245,7 @@ func unmarshalLocalDateTime(x target, node *ast.Node) error { return setLocalDateTime(x, v) } -func unmarshalDateTime(x target, node *ast.Node) error { +func unmarshalDateTime(x target, node ast.Node) error { assertNode(ast.DateTime, node) v, err := parseDateTime(node.Data) if err != nil { @@ -254,18 +262,18 @@ func setDateTime(x target, v time.Time) error { return x.set(reflect.ValueOf(v)) } -func unmarshalString(x target, node *ast.Node) error { +func unmarshalString(x target, node ast.Node) error { assertNode(ast.String, node) return setString(x, string(node.Data)) } -func unmarshalBool(x target, node *ast.Node) error { +func unmarshalBool(x target, node ast.Node) error { assertNode(ast.Bool, node) v := node.Data[0] == 't' return setBool(x, v) } -func unmarshalInteger(x target, node *ast.Node) error { +func unmarshalInteger(x target, node ast.Node) error { assertNode(ast.Integer, node) v, err := parseInteger(node.Data) if err != nil { @@ -274,7 +282,7 @@ func unmarshalInteger(x target, node *ast.Node) error { return setInt64(x, v) } -func unmarshalFloat(x target, node *ast.Node) error { +func unmarshalFloat(x target, node ast.Node) error { assertNode(ast.Float, node) v, err := parseFloat(node.Data) if err != nil { @@ -283,11 +291,13 @@ func unmarshalFloat(x target, node *ast.Node) error { return setFloat64(x, v) } -func (d *decoder) unmarshalInlineTable(x target, node *ast.Node) error { +func (d *decoder) unmarshalInlineTable(x target, node ast.Node) error { assertNode(ast.InlineTable, node) - for _, kv := range node.Children { - err := d.unmarshalKeyValue(x, &kv) + it := node.Children() + for it.Next() { + n := it.Node() + err := d.unmarshalKeyValue(x, n) if err != nil { return err } @@ -295,7 +305,7 @@ func (d *decoder) unmarshalInlineTable(x target, node *ast.Node) error { return nil } -func (d *decoder) unmarshalArray(x target, node *ast.Node) error { +func (d *decoder) unmarshalArray(x target, node ast.Node) error { assertNode(ast.Array, node) err := ensureValueIndexable(x) @@ -303,7 +313,10 @@ func (d *decoder) unmarshalArray(x target, node *ast.Node) error { return err } - for idx, n := range node.Children { + it := node.Children() + idx := 0 + for it.Next() { + n := it.Node() v, err := elementAt(x, idx) if err != nil { return err @@ -313,15 +326,16 @@ func (d *decoder) unmarshalArray(x target, node *ast.Node) error { // mimic encoding/json break } - err = d.unmarshalValue(v, &n) + err = d.unmarshalValue(v, n) if err != nil { return err } + idx++ } return nil } -func assertNode(expected ast.Kind, node *ast.Node) { +func assertNode(expected ast.Kind, node ast.Node) { if node.Kind != expected { panic(fmt.Errorf("expected node of kind %s, not %s", expected, node.Kind)) } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index af1592a..8558e25 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -660,18 +660,18 @@ B = "data"`, } func TestFromAst_KV(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -682,44 +682,44 @@ func TestFromAst_KV(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: "hello"}, x) } func TestFromAst_Table(t *testing.T) { t.Run("one level table on struct", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.Table, - Children: []ast.Node{ + Children: []astNode{ {Kind: ast.Key, Data: []byte(`Level1`)}, }, }, - ast.Node{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`A`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, - }, - }, - ast.Node{ - Kind: ast.KeyValue, - Children: []ast.Node{ { Kind: ast.Key, - Data: []byte(`B`), + Data: []byte(`A`), }, + }, + }, + astNode{ + Kind: ast.KeyValue, + Children: []astNode{ { Kind: ast.String, Data: []byte(`world`), }, + { + Kind: ast.Key, + Data: []byte(`B`), + }, }, }, } @@ -735,7 +735,7 @@ func TestFromAst_Table(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{ Level1: Level1{ @@ -745,25 +745,25 @@ func TestFromAst_Table(t *testing.T) { }, x) }) t.Run("one level table on struct", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.Table, - Children: []ast.Node{ + Children: []astNode{ {Kind: ast.Key, Data: []byte(`A`)}, {Kind: ast.Key, Data: []byte(`B`)}, }, }, - ast.Node{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`C`), - }, + Children: []astNode{ { Kind: ast.String, Data: []byte(`value`), }, + { + Kind: ast.Key, + Data: []byte(`C`), + }, }, }, } @@ -782,7 +782,7 @@ func TestFromAst_Table(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{ A: A{B: B{C: "value"}}, @@ -792,32 +792,33 @@ func TestFromAst_Table(t *testing.T) { func TestFromAst_InlineTable(t *testing.T) { t.Run("one level of strings", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Name`)}, + Children: []astNode{ { Kind: ast.InlineTable, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`First`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Tom`)}, + {Kind: ast.Key, Data: []byte(`First`)}, }, }, { Kind: ast.KeyValue, - Children: []ast.Node{ - {Kind: ast.Key, Data: []byte(`Last`)}, + Children: []astNode{ {Kind: ast.String, Data: []byte(`Preston-Werner`)}, + {Kind: ast.Key, Data: []byte(`Last`)}, }, }, }, }, + { + Kind: ast.Key, + Data: []byte(`Name`), + }, }, }, } @@ -833,7 +834,7 @@ func TestFromAst_InlineTable(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{ Name: Name{ @@ -847,17 +848,13 @@ func TestFromAst_InlineTable(t *testing.T) { func TestFromAst_Slice(t *testing.T) { t.Run("slice of string", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), @@ -868,6 +865,10 @@ func TestFromAst_Slice(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -878,23 +879,19 @@ func TestFromAst_Slice(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []string{"hello", "world"}}, x) }) t.Run("slice of interfaces for strings", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), @@ -905,6 +902,10 @@ func TestFromAst_Slice(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -915,30 +916,26 @@ func TestFromAst_Slice(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", "world"}}, x) }) t.Run("slice of interfaces with slices", func(t *testing.T) { - root := ast.Root{ - ast.Node{ + root := astRoot{ + astNode{ Kind: ast.KeyValue, - Children: []ast.Node{ - { - Kind: ast.Key, - Data: []byte(`Foo`), - }, + Children: []astNode{ { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`hello`), }, { Kind: ast.Array, - Children: []ast.Node{ + Children: []astNode{ { Kind: ast.String, Data: []byte(`inner1`), @@ -951,6 +948,10 @@ func TestFromAst_Slice(t *testing.T) { }, }, }, + { + Kind: ast.Key, + Data: []byte(`Foo`), + }, }, }, } @@ -961,7 +962,7 @@ func TestFromAst_Slice(t *testing.T) { x := Doc{} d := decoder{} - err := d.fromAst(root, &x) + err := d.fromAst(root.toOrig(), &x) require.NoError(t, err) assert.Equal(t, Doc{Foo: []interface{}{"hello", []interface{}{"inner1", "inner2"}}}, x) })