-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathextractor.go
More file actions
134 lines (116 loc) · 4.16 KB
/
Copy pathextractor.go
File metadata and controls
134 lines (116 loc) · 4.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Package forest is the long-tail language adapter. It wraps any
// alexaandru/go-sitter-forest grammar (510+ supported) as a Gortex
// Extractor with signature-only depth: function / method / type /
// interface / variable / constant nodes plus EdgeDefines from the
// file. Calls and imports are best-effort via @reference.call captures
// when the grammar ships a tags.scm.
//
// Top-tier languages (Go, TS, Python, Rust, …) keep their bespoke
// extractors in internal/parser/languages — those have hand-tuned
// queries that emit Gortex-specific edges (ORM, contracts, dataflow)
// the generic walker can't produce. forest is for everything else.
package forest
import (
"sync"
"unsafe"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
sitter "github.com/zzet/gortex/internal/parser/tsitter"
)
// GetLanguageFn returns the raw C *TSLanguage pointer. Every forest
// language module exposes one as `forestpkg.GetLanguage`.
type GetLanguageFn func() unsafe.Pointer
// GetQueryFn returns the bytes of a named .scm query bundled with the
// grammar. Forest grammars ship a subset of {tags, highlights, locals,
// folds, indents, injections}; we only read tags.scm. The opts
// argument is forest's preference flag (NvimFirst / NativeFirst /
// NvimOnly / NativeOnly). nil is acceptable when the grammar has no
// queries.
type GetQueryFn func(kind string, opts ...byte) []byte
// Extractor is a generic forest-backed signature-only extractor.
// Construct one per language via New() and register it with the
// parser registry exactly like any other Extractor.
type Extractor struct {
language string
extensions []string
getLang GetLanguageFn
getQuery GetQueryFn
once sync.Once
initErr error
lang *sitter.Language
tagsQ *parser.PreparedQuery // nil when grammar ships no tags.scm
}
// New builds a forest-backed Extractor. getQuery may be nil for
// grammars that do not expose any .scm queries — extraction then
// falls back to the generic node-kind walker.
func New(language string, extensions []string, getLang GetLanguageFn, getQuery GetQueryFn) *Extractor {
return &Extractor{
language: language,
extensions: extensions,
getLang: getLang,
getQuery: getQuery,
}
}
func (e *Extractor) Language() string { return e.language }
func (e *Extractor) Extensions() []string { return e.extensions }
// init compiles the language pointer + tags.scm query exactly once.
// Errors are sticky: once an init has failed, every subsequent
// Extract returns the same error rather than retrying CGO setup on
// every file (which would slow indexing to a crawl on a broken
// grammar).
func (e *Extractor) init() error {
e.once.Do(func() {
ptr := e.getLang()
if ptr == nil {
e.initErr = errNilLanguage
return
}
e.lang = sitter.NewLanguage(ptr)
if e.getQuery != nil {
pattern := e.getQuery("tags")
if len(pattern) > 0 {
q, err := parser.NewPreparedQuery(string(pattern), e.lang)
if err == nil {
e.tagsQ = q
}
// Compile failure is non-fatal: drop to walker.
}
}
})
return e.initErr
}
// Extract parses src with the bundled grammar and emits one file
// node, plus one node per detected definition (function / method /
// type / interface / variable / constant / field / module). Edges:
// EdgeDefines from the file to every definition; EdgeCalls
// (unresolved::name) for @reference.call captures when tags.scm is
// present.
func (e *Extractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
if err := e.init(); err != nil {
return nil, err
}
tree, err := parser.ParseFile(src, e.lang)
if err != nil {
return nil, err
}
defer tree.Close()
root := tree.RootNode()
result := &parser.ExtractionResult{}
fileNode := &graph.Node{
ID: filePath,
Kind: graph.KindFile,
Name: filePath,
FilePath: filePath,
StartLine: 1,
EndLine: int(root.EndPoint().Row) + 1,
Language: e.language,
}
result.Nodes = append(result.Nodes, fileNode)
if e.tagsQ != nil {
e.extractByTags(root, src, filePath, fileNode, result)
return result, nil
}
e.extractByWalker(root, src, filePath, fileNode, result)
return result, nil
}
var _ parser.Extractor = (*Extractor)(nil)