Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

159 lines
5.6 KiB
Go

package tsitter
// Direct C navigation for the hot child-access path.
//
// go-tree-sitter's Node navigation (Child / NamedChild / Parent, the cursor,
// even the []Node value-slice helpers) funnels through newNode, which
// heap-allocates a *ts.Node for every visited node. Profiling a large index
// put that at ~43% of all bytes allocated — 95% of it NamedChild + Child +
// Parent in the extractor child loops — and the resulting GC churn dominated
// CPU under a memory cap.
//
// The tree-sitter C API returns nodes BY VALUE (TSNode is a 32-byte struct).
// We call those C functions directly and drop the returned value straight
// into the pooled Node arena, so a child visit costs one bump-allocated
// wrapper and zero heap garbage.
//
// The functions are declared here rather than pulled from <tree_sitter/api.h>
// so this file needs no -I path into the go-tree-sitter module cache (which
// is version- and machine-specific). The symbols themselves are defined in
// the tree-sitter C library that go-tree-sitter already compiles and links
// into the binary; C has no name mangling, so a local prototype with the
// identical ABI resolves to them at link time.
/*
#include <stdint.h>
// Mirror of tree-sitter's TSNode (include/tree_sitter/api.h): a 4-word
// context plus two opaque pointers. Layout must match exactly — it is
// reinterpreted from ts.Node, whose sole field is this struct.
typedef struct { uint32_t context[4]; const void *id; const void *tree; } GxTSNode;
// Mirror of TSTreeCursor — reinterpreted from *ts.TreeCursor (whose sole
// field is this struct) so the cursor's current node can be read by value.
typedef struct { const void *tree; const void *id; uint32_t context[3]; } GxTSTreeCursor;
extern GxTSNode ts_node_child(GxTSNode, uint32_t);
extern GxTSNode ts_node_named_child(GxTSNode, uint32_t);
extern GxTSNode ts_node_parent(GxTSNode);
extern GxTSNode ts_node_next_sibling(GxTSNode);
extern GxTSNode ts_node_prev_sibling(GxTSNode);
extern GxTSNode ts_node_next_named_sibling(GxTSNode);
extern GxTSNode ts_node_prev_named_sibling(GxTSNode);
extern GxTSNode ts_node_child_by_field_name(GxTSNode, const char *, uint32_t);
extern GxTSNode ts_tree_cursor_current_node(const GxTSTreeCursor *);
*/
import "C"
import (
"unsafe"
ts "github.com/tree-sitter/go-tree-sitter"
)
// init fails fast if a tree-sitter upgrade ever changes the TSNode layout
// out from under the reinterpret casts below, rather than silently corrupting
// navigated nodes at runtime.
func init() {
if unsafe.Sizeof(ts.Node{}) != unsafe.Sizeof(C.GxTSNode{}) {
panic("tsitter: ts.Node and TSNode size mismatch — tree-sitter ABI changed, update node_cnav.go")
}
if unsafe.Sizeof(ts.TreeCursor{}) != unsafe.Sizeof(C.GxTSTreeCursor{}) {
panic("tsitter: ts.TreeCursor and TSTreeCursor size mismatch — tree-sitter ABI changed, update node_cnav.go")
}
}
// asC reinterprets an upstream ts.Node as the locally-declared C node. ts.Node
// is `struct { _inner C.TSNode }`, so its bytes are exactly a TSNode and the
// two share an identical ABI layout.
func asC(n ts.Node) C.GxTSNode { return *(*C.GxTSNode)(unsafe.Pointer(&n)) }
// asGo reinterprets a C node back into an upstream ts.Node value.
func asGo(c C.GxTSNode) ts.Node { return *(*ts.Node)(unsafe.Pointer(&c)) }
// childDirect returns parent's i-th child as a value, with no heap
// allocation. ok is false for a null child (index past the end).
func childDirect(parent ts.Node, i int) (ts.Node, bool) {
c := C.ts_node_child(asC(parent), C.uint32_t(i))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
// namedChildDirect is childDirect over named children only.
func namedChildDirect(parent ts.Node, i int) (ts.Node, bool) {
c := C.ts_node_named_child(asC(parent), C.uint32_t(i))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
// parentDirect returns n's parent as a value. ok is false at the root.
func parentDirect(n ts.Node) (ts.Node, bool) {
c := C.ts_node_parent(asC(n))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
func nextSiblingDirect(n ts.Node) (ts.Node, bool) {
c := C.ts_node_next_sibling(asC(n))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
func prevSiblingDirect(n ts.Node) (ts.Node, bool) {
c := C.ts_node_prev_sibling(asC(n))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
func nextNamedSiblingDirect(n ts.Node) (ts.Node, bool) {
c := C.ts_node_next_named_sibling(asC(n))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
func prevNamedSiblingDirect(n ts.Node) (ts.Node, bool) {
c := C.ts_node_prev_named_sibling(asC(n))
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}
// cursorCurrentNode returns a tree cursor's current node by value, avoiding
// TreeCursor.Node's per-step heap *Node. The cursor walk itself (GotoFirstChild
// / GotoNextSibling) stays O(total children); only the node read is changed.
func cursorCurrentNode(cursor *ts.TreeCursor) ts.Node {
return asGo(C.ts_tree_cursor_current_node((*C.GxTSTreeCursor)(unsafe.Pointer(cursor))))
}
// childByFieldNameDirect returns parent's child for a grammar field name. The
// name's bytes are passed to C by pointer+length (ts_node_child_by_field_name
// does not require NUL termination and reads them only for the duration of the
// call), so no C string is allocated. ok is false when no such field exists.
func childByFieldNameDirect(parent ts.Node, name string) (ts.Node, bool) {
if name == "" {
return ts.Node{}, false
}
c := C.ts_node_child_by_field_name(
asC(parent),
(*C.char)(unsafe.Pointer(unsafe.StringData(name))),
C.uint32_t(len(name)),
)
if c.id == nil {
return ts.Node{}, false
}
return asGo(c), true
}