Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
248 changes: 248 additions & 0 deletions oracle/ast/cmd/genwalker/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// Command genwalker generates walk_generated.go from parsenodes.go and node.go.
//
// It scans all struct types, identifies fields whose types are Node-like
// (Node, ExprNode, TableExpr interfaces; *List; pointers to AST structs;
// slices of Node-like types), and generates the walkChildren function.
//
// Usage:
//
// go run ./oracle/ast/cmd/genwalker
package main

import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"sort"
"strings"
)

// nodeInterfaces are the interface types that represent walkable child nodes.
var nodeInterfaces = map[string]bool{
"Node": true,
"ExprNode": true,
"TableExpr": true,
"StmtNode": true,
}

// excludedStructs are struct types that are NOT AST nodes (no walking needed).
var excludedStructs = map[string]bool{
"Loc": true,
"List": true, // List is walked via walkList, not walkChildren
"String": true, // value node, no children
"Integer": true, // value node, no children
"Float": true, // value node, no children
"Boolean": true, // value node, no children
}

func main() {
fset := token.NewFileSet()

sources := []string{"parsenodes.go", "node.go"}
var files []*ast.File
for _, src := range sources {
f, err := parser.ParseFile(fset, src, nil, 0)
if err != nil {
fmt.Fprintf(os.Stderr, "parse %s: %v\n", src, err)
os.Exit(1)
}
files = append(files, f)
}

// Collect all struct type names.
structNames := map[string]bool{}
type field struct {
Name string
Type string // e.g., "Node", "ExprNode", "*SelectStmt", "[]ExprNode", "[]*OrderByItem"
Kind string // "interface", "pointer", "slice_interface", "slice_pointer", "*List"
}
type structInfo struct {
Name string
Fields []field
}

for _, f := range files {
for _, decl := range f.Decls {
gd, ok := decl.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
continue
}
for _, spec := range gd.Specs {
ts := spec.(*ast.TypeSpec)
if _, ok := ts.Type.(*ast.StructType); ok {
structNames[ts.Name.Name] = true
}
}
}
}

var structs []structInfo

for _, f := range files {
for _, decl := range f.Decls {
gd, ok := decl.(*ast.GenDecl)
if !ok || gd.Tok != token.TYPE {
continue
}
for _, spec := range gd.Specs {
ts := spec.(*ast.TypeSpec)
st, ok := ts.Type.(*ast.StructType)
if !ok {
continue
}

var fields []field
for _, fl := range st.Fields.List {
if len(fl.Names) == 0 {
continue // embedded
}
typStr := typeString(fl.Type)
kind := classifyType(typStr, structNames)
if kind != "" {
for _, name := range fl.Names {
fields = append(fields, field{Name: name.Name, Type: typStr, Kind: kind})
}
}
}
structs = append(structs, structInfo{Name: ts.Name.Name, Fields: fields})
}
}
}

sort.Slice(structs, func(i, j int) bool {
return structs[i].Name < structs[j].Name
})

// Generate code.
var buf bytes.Buffer
buf.WriteString("// Code generated by genwalker; DO NOT EDIT.\n\n")
buf.WriteString("package ast\n\n")
buf.WriteString("// walkChildren walks the child nodes of node, calling Walk(v, child)\n")
buf.WriteString("// for each child. This function is generated from parsenodes.go.\n")
buf.WriteString("func walkChildren(v Visitor, node Node) {\n")
buf.WriteString("\tswitch n := node.(type) {\n")

for _, s := range structs {
if len(s.Fields) == 0 {
continue
}
fmt.Fprintf(&buf, "\tcase *%s:\n", s.Name)
for _, f := range s.Fields {
switch f.Kind {
case "interface":
// Node, ExprNode, TableExpr, StmtNode — Walk handles nil via interface check
fmt.Fprintf(&buf, "\t\tWalk(v, n.%s)\n", f.Name)
case "*List":
fmt.Fprintf(&buf, "\t\twalkList(v, n.%s)\n", f.Name)
case "pointer":
// *ConcreteStruct — need nil check
fmt.Fprintf(&buf, "\t\tif n.%s != nil {\n", f.Name)
fmt.Fprintf(&buf, "\t\t\tWalk(v, n.%s)\n", f.Name)
fmt.Fprintf(&buf, "\t\t}\n")
case "slice_interface":
// []ExprNode, []TableExpr, []Node — iterate and walk each
fmt.Fprintf(&buf, "\t\tfor _, item := range n.%s {\n", f.Name)
fmt.Fprintf(&buf, "\t\t\tWalk(v, item)\n")
fmt.Fprintf(&buf, "\t\t}\n")
case "slice_pointer":
// []*ConcreteStruct — iterate, nil check, walk
fmt.Fprintf(&buf, "\t\tfor _, item := range n.%s {\n", f.Name)
fmt.Fprintf(&buf, "\t\t\tif item != nil {\n")
fmt.Fprintf(&buf, "\t\t\t\tWalk(v, item)\n")
fmt.Fprintf(&buf, "\t\t\t}\n")
fmt.Fprintf(&buf, "\t\t}\n")
}
}
}

buf.WriteString("\t}\n")
buf.WriteString("}\n")

formatted, err := format.Source(buf.Bytes())
if err != nil {
fmt.Fprintf(os.Stderr, "format: %v\n", err)
os.WriteFile("walk_generated.go", buf.Bytes(), 0644)
os.Exit(1)
}

if err := os.WriteFile("walk_generated.go", formatted, 0644); err != nil {
fmt.Fprintf(os.Stderr, "write: %v\n", err)
os.Exit(1)
}

cases := 0
fields := 0
for _, s := range structs {
if len(s.Fields) > 0 {
cases++
fields += len(s.Fields)
}
}
fmt.Printf("Generated walk_generated.go: %d cases, %d child fields\n", cases, fields)
}

// typeString returns the string representation of a Go type expression.
func typeString(expr ast.Expr) string {
switch t := expr.(type) {
case *ast.Ident:
return t.Name
case *ast.StarExpr:
return "*" + typeString(t.X)
case *ast.SelectorExpr:
return typeString(t.X) + "." + t.Sel.Name
case *ast.ArrayType:
return "[]" + typeString(t.Elt)
default:
return ""
}
}

// classifyType returns the walk kind for a field type, or "" if not walkable.
func classifyType(typStr string, structNames map[string]bool) string {
// Interface types: Node, ExprNode, TableExpr, StmtNode
if nodeInterfaces[typStr] {
return "interface"
}

// *List
if typStr == "*List" {
return "*List"
}

// Pointer to known struct: *SelectStmt, *Limit, etc.
if strings.HasPrefix(typStr, "*") {
name := typStr[1:]
if excludedStructs[name] {
return ""
}
if structNames[name] {
return "pointer"
}
return ""
}

// Slice of interface: []ExprNode, []TableExpr, []Node
if strings.HasPrefix(typStr, "[]") {
elemType := typStr[2:]
if nodeInterfaces[elemType] {
return "slice_interface"
}
// Slice of pointer to known struct: []*OrderByItem, []*WindowDef
if strings.HasPrefix(elemType, "*") {
structName := elemType[1:]
if excludedStructs[structName] {
return ""
}
if structNames[structName] {
return "slice_pointer"
}
}
return ""
}

return ""
}
57 changes: 57 additions & 0 deletions oracle/ast/walk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//go:generate go run ./cmd/genwalker

package ast

// Visitor defines the interface for AST traversal.
// Visit is called for each node during a depth-first walk.
// If Visit returns a non-nil Visitor, Walk recurses into the node's children
// with the returned Visitor, then calls Visit(nil) to signal post-order.
// If Visit returns nil, children are not visited.
type Visitor interface {
Visit(node Node) Visitor
}

// Walk traverses an AST in depth-first order. It calls v.Visit(node);
// if that returns a non-nil visitor w, it walks each child node with w,
// then calls w.Visit(nil).
func Walk(v Visitor, node Node) {
if node == nil {
return
}
w := v.Visit(node)
if w == nil {
return
}
walkChildren(w, node)
w.Visit(nil)
}

// Inspect traverses an AST in depth-first order, calling f for each node.
// If f returns true, Inspect recurses into the node's children.
func Inspect(node Node, f func(Node) bool) {
Walk(inspector(f), node)
}

type inspector func(Node) bool

func (f inspector) Visit(node Node) Visitor {
if node != nil && f(node) {
return f
}
return nil
}

// walkList visits a List node and then walks each of its items.
func walkList(v Visitor, list *List) {
if list == nil {
return
}
w := v.Visit(list)
if w == nil {
return
}
for _, item := range list.Items {
Walk(w, item)
}
w.Visit(nil)
}
Loading
Loading