Linear array storage for AST

This commit is contained in:
Thomas Pelletier
2021-03-25 19:56:02 -04:00
parent 8a8d1233bb
commit 1bae751a45
7 changed files with 659 additions and 460 deletions
+100 -177
View File
@@ -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("<f%d> %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("<f%d> %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))
}
+58
View File
@@ -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
}
+69
View File
@@ -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))
}