2025-11-08 09:37:30 +03:00

279 lines
9.8 KiB
Go

package ast
import (
"slices"
)
// NodeVisitor
type NodeVisitor struct {
Visit func(node *Node) *Node // Required. The callback used to visit a node
Factory *NodeFactory // Required. The NodeFactory used to produce new nodes when passed to VisitEachChild
Hooks NodeVisitorHooks // Hooks to be invoked when visiting a node
}
// These hooks are used to intercept the default behavior of the visitor
type NodeVisitorHooks struct {
VisitNode func(node *Node, v *NodeVisitor) *Node // Overrides visiting a Node. Only invoked by the VisitEachChild method on a given Node subtype.
VisitToken func(node *TokenNode, v *NodeVisitor) *Node // Overrides visiting a TokenNode. Only invoked by the VisitEachChild method on a given Node subtype.
VisitNodes func(nodes *NodeList, v *NodeVisitor) *NodeList // Overrides visiting a NodeList. Only invoked by the VisitEachChild method on a given Node subtype.
VisitModifiers func(nodes *ModifierList, v *NodeVisitor) *ModifierList // Overrides visiting a ModifierList. Only invoked by the VisitEachChild method on a given Node subtype.
VisitEmbeddedStatement func(node *Statement, v *NodeVisitor) *Statement // Overrides visiting a Node when it is the embedded statement body of an iteration statement, `if` statement, or `with` statement. Only invoked by the VisitEachChild method on a given Node subtype.
VisitIterationBody func(node *Statement, v *NodeVisitor) *Statement // Overrides visiting a Node when it is the embedded statement body of an iteration statement. Only invoked by the VisitEachChild method on a given Node subtype.
VisitParameters func(nodes *ParameterList, v *NodeVisitor) *ParameterList // Overrides visiting a ParameterList. Only invoked by the VisitEachChild method on a given Node subtype.
VisitFunctionBody func(node *BlockOrExpression, v *NodeVisitor) *BlockOrExpression // Overrides visiting a function body. Only invoked by the VisitEachChild method on a given Node subtype.
VisitTopLevelStatements func(nodes *StatementList, v *NodeVisitor) *StatementList // Overrides visiting a variable environment. Only invoked by the VisitEachChild method on a given Node subtype.
}
func NewNodeVisitor(visit func(node *Node) *Node, factory *NodeFactory, hooks NodeVisitorHooks) *NodeVisitor {
if factory == nil {
factory = &NodeFactory{}
}
return &NodeVisitor{Visit: visit, Factory: factory, Hooks: hooks}
}
func (v *NodeVisitor) VisitSourceFile(node *SourceFile) *SourceFile {
return v.VisitNode(node.AsNode()).AsSourceFile()
}
// Visits a Node, possibly returning a new Node in its place.
//
// - If the input node is nil, then the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, then the output is nil.
// - If v.Visit returns a SyntaxList Node, then the output is the only child of the SyntaxList Node.
func (v *NodeVisitor) VisitNode(node *Node) *Node {
if node == nil || v.Visit == nil {
return node
}
if v.Visit != nil {
visited := v.Visit(node)
if visited != nil && visited.Kind == KindSyntaxList {
nodes := visited.AsSyntaxList().Children
if len(nodes) != 1 {
panic("Expected only a single node to be written to output")
}
visited = nodes[0]
if visited != nil && visited.Kind == KindSyntaxList {
panic("The result of visiting and lifting a Node may not be SyntaxList")
}
}
return visited
}
return node
}
// Visits an embedded Statement (i.e., the single statement body of a loop, `if..else` branch, etc.), possibly returning a new Statement in its place.
//
// - If the input node is nil, then the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, then the output is nil.
// - If v.Visit returns a SyntaxList Node, then the output is either the only child of the SyntaxList Node, or a Block containing the nodes in the list.
func (v *NodeVisitor) VisitEmbeddedStatement(node *Statement) *Statement {
if node == nil || v.Visit == nil {
return node
}
if v.Visit != nil {
return v.liftToBlock(v.Visit(node))
}
return node
}
// Visits a NodeList, possibly returning a new NodeList in its place.
//
// - If the input NodeList is nil, the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, the visited Node will be absent in the output.
// - If v.Visit returns a different Node than the input, a new NodeList will be generated and returned.
// - If v.Visit returns a SyntaxList Node, then the children of that node will be merged into the output and a new NodeList will be returned.
// - If this method returns a new NodeList for any reason, it will have the same Loc as the input NodeList.
func (v *NodeVisitor) VisitNodes(nodes *NodeList) *NodeList {
if nodes == nil || v.Visit == nil {
return nodes
}
if result, changed := v.VisitSlice(nodes.Nodes); changed {
list := v.Factory.NewNodeList(result)
list.Loc = nodes.Loc
return list
}
return nodes
}
// Visits a ModifierList, possibly returning a new ModifierList in its place.
//
// - If the input ModifierList is nil, the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, the visited Node will be absent in the output.
// - If v.Visit returns a different Node than the input, a new ModifierList will be generated and returned.
// - If v.Visit returns a SyntaxList Node, then the children of that node will be merged into the output and a new NodeList will be returned.
// - If this method returns a new NodeList for any reason, it will have the same Loc as the input NodeList.
func (v *NodeVisitor) VisitModifiers(nodes *ModifierList) *ModifierList {
if nodes == nil || v.Visit == nil {
return nodes
}
if result, changed := v.VisitSlice(nodes.Nodes); changed {
list := v.Factory.NewModifierList(result)
list.Loc = nodes.Loc
return list
}
return nodes
}
// Visits a slice of Nodes, returning the resulting slice and a value indicating whether the slice was changed.
//
// - If the input slice is nil, the output is nil.
// - If v.Visit is nil, then the output is the input.
// - If v.Visit returns nil, the visited Node will be absent in the output.
// - If v.Visit returns a different Node than the input, a new slice will be generated and returned.
// - If v.Visit returns a SyntaxList Node, then the children of that node will be merged into the output and a new slice will be returned.
func (v *NodeVisitor) VisitSlice(nodes []*Node) (result []*Node, changed bool) {
if nodes == nil || v.Visit == nil {
return nodes, false
}
for i := 0; i < len(nodes); i++ {
node := nodes[i]
if v.Visit == nil {
break
}
visited := v.Visit(node)
if visited == nil || visited != node {
updated := slices.Clone(nodes[:i])
for {
// finish prior loop
switch {
case visited == nil: // do nothing
case visited.Kind == KindSyntaxList:
updated = append(updated, visited.AsSyntaxList().Children...)
default:
updated = append(updated, visited)
}
i++
// loop over remaining elements
if i >= len(nodes) {
break
}
if v.Visit != nil {
node = nodes[i]
visited = v.Visit(node)
} else {
updated = append(updated, nodes[i:]...)
break
}
}
return updated, true
}
}
return nodes, false
}
// Visits each child of a Node, possibly returning a new Node of the same kind in its place.
func (v *NodeVisitor) VisitEachChild(node *Node) *Node {
if node == nil || v.Visit == nil {
return node
}
return node.VisitEachChild(v)
}
func (v *NodeVisitor) visitNode(node *Node) *Node {
if v.Hooks.VisitNode != nil {
return v.Hooks.VisitNode(node, v)
}
return v.VisitNode(node)
}
func (v *NodeVisitor) visitEmbeddedStatement(node *Node) *Node {
if v.Hooks.VisitEmbeddedStatement != nil {
return v.Hooks.VisitEmbeddedStatement(node, v)
}
if v.Hooks.VisitNode != nil {
return v.liftToBlock(v.Hooks.VisitNode(node, v))
}
return v.VisitEmbeddedStatement(node)
}
func (v *NodeVisitor) visitIterationBody(node *Statement) *Statement {
if v.Hooks.VisitIterationBody != nil {
return v.Hooks.VisitIterationBody(node, v)
}
return v.visitEmbeddedStatement(node)
}
func (v *NodeVisitor) visitFunctionBody(node *BlockOrExpression) *BlockOrExpression {
if v.Hooks.VisitFunctionBody != nil {
return v.Hooks.VisitFunctionBody(node, v)
}
return v.visitNode(node)
}
func (v *NodeVisitor) visitToken(node *Node) *Node {
if v.Hooks.VisitToken != nil {
return v.Hooks.VisitToken(node, v)
}
return v.VisitNode(node)
}
func (v *NodeVisitor) visitNodes(nodes *NodeList) *NodeList {
if v.Hooks.VisitNodes != nil {
return v.Hooks.VisitNodes(nodes, v)
}
return v.VisitNodes(nodes)
}
func (v *NodeVisitor) visitModifiers(nodes *ModifierList) *ModifierList {
if v.Hooks.VisitModifiers != nil {
return v.Hooks.VisitModifiers(nodes, v)
}
return v.VisitModifiers(nodes)
}
func (v *NodeVisitor) visitParameters(nodes *ParameterList) *ParameterList {
if v.Hooks.VisitParameters != nil {
return v.Hooks.VisitParameters(nodes, v)
}
return v.visitNodes(nodes)
}
func (v *NodeVisitor) visitTopLevelStatements(nodes *StatementList) *StatementList {
if v.Hooks.VisitTopLevelStatements != nil {
return v.Hooks.VisitTopLevelStatements(nodes, v)
}
return v.visitNodes(nodes)
}
func (v *NodeVisitor) liftToBlock(node *Statement) *Statement {
var nodes []*Node
if node != nil {
if node.Kind == KindSyntaxList {
nodes = node.AsSyntaxList().Children
} else {
nodes = []*Node{node}
}
}
if len(nodes) == 1 {
node = nodes[0]
} else {
node = v.Factory.NewBlock(v.Factory.NewNodeList(nodes), true /*multiLine*/)
}
if node.Kind == KindSyntaxList {
panic("The result of visiting and lifting a Node may not be SyntaxList")
}
return node
}