chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,554 @@
|
||||
package languages
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
pascalforest "github.com/alexaandru/go-sitter-forest/pascal"
|
||||
|
||||
"github.com/zzet/gortex/internal/graph"
|
||||
"github.com/zzet/gortex/internal/parser"
|
||||
sitter "github.com/zzet/gortex/internal/parser/tsitter"
|
||||
)
|
||||
|
||||
// Pascal / Object Pascal / Delphi. Built on the tree-sitter Pascal grammar so
|
||||
// every call edge (paren `Foo(x)` AND paren-less `Foo;`), enum member, class
|
||||
// member, field, constant, visibility section, and return type is recovered —
|
||||
// where the prior regex extractor emitted zero call edges. Pascal identifiers
|
||||
// are case-insensitive, so in-file call resolution keys on the lowercased name.
|
||||
type PascalExtractor struct {
|
||||
lang *sitter.Language
|
||||
}
|
||||
|
||||
func NewPascalExtractor() *PascalExtractor {
|
||||
return &PascalExtractor{lang: sitter.NewLanguage(pascalforest.GetLanguage())}
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) Language() string { return "pascal" }
|
||||
func (e *PascalExtractor) Extensions() []string {
|
||||
return []string{".pas", ".pp", ".dpr", ".dpk", ".inc", ".lpr", ".lfm"}
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
|
||||
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: "pascal",
|
||||
}
|
||||
result.Nodes = append(result.Nodes, fileNode)
|
||||
|
||||
seen := map[string]bool{}
|
||||
// procIndex maps a lowercased callable name (bare and class-qualified) to
|
||||
// its node ID, so a same-file call resolves directly to the definition.
|
||||
procIndex := map[string]string{}
|
||||
|
||||
// Pass 1 — declarations. Builds procIndex for pass 2.
|
||||
walkNodes(root, func(n *sitter.Node) {
|
||||
switch n.Type() {
|
||||
case "unit", "program", "library", "package":
|
||||
e.emitPascalModule(n, src, filePath, fileNode.ID, result, seen)
|
||||
case "declUses":
|
||||
e.emitPascalUses(n, src, filePath, fileNode.ID, result)
|
||||
case "declType":
|
||||
e.emitPascalType(n, src, filePath, fileNode.ID, result, seen, procIndex)
|
||||
case "declConst":
|
||||
e.emitPascalConst(n, src, filePath, fileNode.ID, result, seen)
|
||||
case "defProc":
|
||||
e.emitPascalProcDef(n, src, filePath, fileNode.ID, result, seen, procIndex)
|
||||
}
|
||||
})
|
||||
|
||||
// Pass 2 — call edges from each implementation body, now that every
|
||||
// in-file callable is known.
|
||||
walkNodes(root, func(n *sitter.Node) {
|
||||
if n.Type() == "defProc" {
|
||||
e.emitPascalCalls(n, src, filePath, result, procIndex)
|
||||
}
|
||||
})
|
||||
|
||||
// Same-file constant value references → impact-radius reads.
|
||||
captureValueRefCandidates(result, root, filePath, src)
|
||||
captureFnValueCandidates(result, root, filePath, src)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// --- declaration emitters ------------------------------------------------
|
||||
|
||||
func (e *PascalExtractor) emitPascalModule(n *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult, seen map[string]bool) {
|
||||
name := pascalChildText(n, src, "moduleName")
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
id := filePath + "::" + name
|
||||
if seen[id] {
|
||||
return
|
||||
}
|
||||
seen[id] = true
|
||||
line := int(n.StartPoint().Row) + 1
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: graph.KindPackage, Name: name,
|
||||
FilePath: filePath, StartLine: line, EndLine: int(n.EndPoint().Row) + 1, Language: "pascal",
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: line,
|
||||
})
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) emitPascalUses(n *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult) {
|
||||
for i, _nc := 0, int(n.ChildCount()); i < _nc; i++ {
|
||||
c := n.Child(i)
|
||||
if c.Type() != "moduleName" {
|
||||
continue
|
||||
}
|
||||
mod := strings.TrimSpace(c.Content(src))
|
||||
if mod == "" {
|
||||
continue
|
||||
}
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fileID, To: "unresolved::import::" + mod,
|
||||
Kind: graph.EdgeImports, FilePath: filePath, Line: int(c.StartPoint().Row) + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) emitPascalConst(n *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult, seen map[string]bool) {
|
||||
name := pascalChildText(n, src, "identifier")
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
id, ok := disambiguateID(seen, filePath+"::"+name, int(n.StartPoint().Row)+1)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
line := int(n.StartPoint().Row) + 1
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: graph.KindConstant, Name: name,
|
||||
FilePath: filePath, StartLine: line, EndLine: int(n.EndPoint().Row) + 1, Language: "pascal",
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: line,
|
||||
})
|
||||
}
|
||||
|
||||
// emitPascalType handles `TName = <type>`: an enum, a class/object/record, an
|
||||
// interface, or a plain alias. Enum members, class fields and methods, the base
|
||||
// type, and visibility are emitted; class methods are indexed for call
|
||||
// resolution.
|
||||
func (e *PascalExtractor) emitPascalType(n *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult, seen map[string]bool, procIndex map[string]string) {
|
||||
name := pascalChildText(n, src, "identifier")
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
typeID := filePath + "::" + name
|
||||
if seen[typeID] {
|
||||
return
|
||||
}
|
||||
body := pascalTypeBody(n)
|
||||
kind := graph.KindType
|
||||
if body != nil && body.Type() == "declInterface" {
|
||||
kind = graph.KindInterface
|
||||
}
|
||||
seen[typeID] = true
|
||||
line := int(n.StartPoint().Row) + 1
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: typeID, Kind: kind, Name: name,
|
||||
FilePath: filePath, StartLine: line, EndLine: int(n.EndPoint().Row) + 1, Language: "pascal",
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fileID, To: typeID, Kind: graph.EdgeDefines, FilePath: filePath, Line: line,
|
||||
})
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
switch body.Type() {
|
||||
case "declEnum":
|
||||
e.emitPascalEnumMembers(body, src, filePath, typeID, result, seen)
|
||||
case "declClass", "declRecord", "declObject", "declInterface":
|
||||
e.emitPascalClassBody(body, src, filePath, name, typeID, result, seen, procIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) emitPascalEnumMembers(enum *sitter.Node, src []byte, filePath, typeID string, result *parser.ExtractionResult, seen map[string]bool) {
|
||||
for i, _nc := 0, int(enum.ChildCount()); i < _nc; i++ {
|
||||
c := enum.Child(i)
|
||||
if c.Type() != "declEnumValue" {
|
||||
continue
|
||||
}
|
||||
mname := pascalChildText(c, src, "identifier")
|
||||
if mname == "" {
|
||||
mname = strings.TrimSpace(c.Content(src))
|
||||
}
|
||||
if mname == "" {
|
||||
continue
|
||||
}
|
||||
line := int(c.StartPoint().Row) + 1
|
||||
id, ok := disambiguateID(seen, filePath+"::"+mname, line)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: graph.KindEnumMember, Name: mname,
|
||||
FilePath: filePath, StartLine: line, EndLine: line, Language: "pascal",
|
||||
Meta: map[string]any{"enum": typeID},
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: id, To: typeID, Kind: graph.EdgeMemberOf, FilePath: filePath, Line: line,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) emitPascalClassBody(body *sitter.Node, src []byte, filePath, typeName, typeID string, result *parser.ExtractionResult, seen map[string]bool, procIndex map[string]string) {
|
||||
// Base type(s): typeref children directly under the class header.
|
||||
for i, _nc := 0, int(body.ChildCount()); i < _nc; i++ {
|
||||
c := body.Child(i)
|
||||
if c.Type() == "typeref" {
|
||||
base := strings.TrimSpace(c.Content(src))
|
||||
if base != "" {
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: typeID, To: "unresolved::" + base, Kind: graph.EdgeExtends,
|
||||
FilePath: filePath, Line: int(c.StartPoint().Row) + 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
visibility := VisibilityPublic
|
||||
var walk func(n *sitter.Node)
|
||||
walk = func(n *sitter.Node) {
|
||||
for i, _nc := 0, int(n.ChildCount()); i < _nc; i++ {
|
||||
c := n.Child(i)
|
||||
switch c.Type() {
|
||||
case "declSection":
|
||||
walk(c)
|
||||
case "kPrivate":
|
||||
visibility = VisibilityPrivate
|
||||
case "kProtected":
|
||||
visibility = VisibilityProtected
|
||||
case "kPublic", "kPublished":
|
||||
visibility = VisibilityPublic
|
||||
case "declField":
|
||||
e.emitPascalField(c, src, filePath, typeName, typeID, visibility, result, seen)
|
||||
case "declProc":
|
||||
e.emitPascalMethodDecl(c, src, filePath, typeName, typeID, visibility, result, seen, procIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(body)
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) emitPascalField(n *sitter.Node, src []byte, filePath, typeName, typeID, visibility string, result *parser.ExtractionResult, seen map[string]bool) {
|
||||
fname := pascalChildText(n, src, "identifier")
|
||||
if fname == "" {
|
||||
return
|
||||
}
|
||||
line := int(n.StartPoint().Row) + 1
|
||||
id, ok := disambiguateID(seen, filePath+"::"+typeName+"."+fname, line)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
meta := map[string]any{"receiver": typeName, "visibility": visibility}
|
||||
if ft := pascalChildText(n, src, "type"); ft != "" {
|
||||
meta["field_type"] = ft
|
||||
}
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: graph.KindField, Name: fname,
|
||||
FilePath: filePath, StartLine: line, EndLine: line, Language: "pascal", Meta: meta,
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: id, To: typeID, Kind: graph.EdgeMemberOf, FilePath: filePath, Line: line,
|
||||
})
|
||||
}
|
||||
|
||||
// emitPascalMethodDecl emits a method node from a class-body declProc signature.
|
||||
func (e *PascalExtractor) emitPascalMethodDecl(n *sitter.Node, src []byte, filePath, typeName, typeID, visibility string, result *parser.ExtractionResult, seen map[string]bool, procIndex map[string]string) {
|
||||
mname := pascalProcLocalName(n, src)
|
||||
if mname == "" {
|
||||
return
|
||||
}
|
||||
line := int(n.StartPoint().Row) + 1
|
||||
id := filePath + "::" + typeName + "." + mname
|
||||
pascalIndex(procIndex, mname, id)
|
||||
pascalIndex(procIndex, typeName+"."+mname, id)
|
||||
if seen[id] {
|
||||
return
|
||||
}
|
||||
seen[id] = true
|
||||
meta := map[string]any{"receiver": typeName, "visibility": visibility}
|
||||
if rt := pascalReturnType(n, src); rt != "" {
|
||||
meta["return_type"] = rt
|
||||
} else if pascalFirstChild(n, "kConstructor") != nil {
|
||||
// A constructor yields an instance of its class — seed the
|
||||
// chained-factory walker (`TFoo.Create.Configure`) from the type.
|
||||
meta["return_type"] = typeName
|
||||
}
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: graph.KindMethod, Name: mname,
|
||||
FilePath: filePath, StartLine: line, EndLine: int(n.EndPoint().Row) + 1, Language: "pascal", Meta: meta,
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: filePath, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: line,
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: id, To: typeID, Kind: graph.EdgeMemberOf, FilePath: filePath, Line: line,
|
||||
})
|
||||
}
|
||||
|
||||
// emitPascalProcDef emits a node for an implementation proc/function. A
|
||||
// class-qualified one (`function TFoo.Bar`) whose class declared it earlier is
|
||||
// not re-emitted, but is indexed so its body's calls resolve; a free proc or an
|
||||
// out-of-file method is emitted here.
|
||||
func (e *PascalExtractor) emitPascalProcDef(n *sitter.Node, src []byte, filePath, fileID string, result *parser.ExtractionResult, seen map[string]bool, procIndex map[string]string) {
|
||||
decl := pascalFirstChild(n, "declProc")
|
||||
if decl == nil {
|
||||
return
|
||||
}
|
||||
qualified, owner, bare := pascalProcQualifiedName(decl, src)
|
||||
if bare == "" {
|
||||
return
|
||||
}
|
||||
id := filePath + "::" + qualified
|
||||
line := int(n.StartPoint().Row) + 1
|
||||
pascalIndex(procIndex, bare, id)
|
||||
pascalIndex(procIndex, qualified, id)
|
||||
if seen[id] {
|
||||
return
|
||||
}
|
||||
seen[id] = true
|
||||
kind := graph.KindFunction
|
||||
meta := map[string]any{}
|
||||
if owner != "" {
|
||||
kind = graph.KindMethod
|
||||
meta["receiver"] = owner
|
||||
}
|
||||
if rt := pascalReturnType(decl, src); rt != "" {
|
||||
meta["return_type"] = rt
|
||||
}
|
||||
result.Nodes = append(result.Nodes, &graph.Node{
|
||||
ID: id, Kind: kind, Name: bare,
|
||||
FilePath: filePath, StartLine: line, EndLine: int(n.EndPoint().Row) + 1, Language: "pascal", Meta: meta,
|
||||
})
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fileID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: line,
|
||||
})
|
||||
if owner != "" {
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: id, To: filePath + "::" + owner, Kind: graph.EdgeMemberOf, FilePath: filePath, Line: line,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- call edges ----------------------------------------------------------
|
||||
|
||||
func (e *PascalExtractor) emitPascalCalls(n *sitter.Node, src []byte, filePath string, result *parser.ExtractionResult, procIndex map[string]string) {
|
||||
decl := pascalFirstChild(n, "declProc")
|
||||
if decl == nil {
|
||||
return
|
||||
}
|
||||
qualified, _, bare := pascalProcQualifiedName(decl, src)
|
||||
if bare == "" {
|
||||
return
|
||||
}
|
||||
fromID := filePath + "::" + qualified
|
||||
block := pascalFirstChild(n, "block")
|
||||
if block == nil {
|
||||
return
|
||||
}
|
||||
walkNodes(block, func(c *sitter.Node) {
|
||||
switch c.Type() {
|
||||
case "exprCall":
|
||||
// First named child is the callee (identifier or genericDot).
|
||||
if callee := pascalCallTargetName(c, src); callee != "" {
|
||||
e.emitPascalCallEdge(fromID, callee, filePath, int(c.StartPoint().Row)+1, result, procIndex)
|
||||
} else if dot := pascalFirstChild(c, "exprDot"); dot != nil {
|
||||
// A chained `recv.Method()` whose receiver is itself a call --
|
||||
// a factory chain Builder().WithX().Build(). The bare-callee path
|
||||
// above misses these; emit the method with the receiver text so
|
||||
// the shared walker can type the chain.
|
||||
method, receiver := pascalDotMethodReceiver(dot, src)
|
||||
if method != "" && strings.Contains(receiver, "(") {
|
||||
edge := &graph.Edge{
|
||||
From: fromID, To: "unresolved::*." + method,
|
||||
Kind: graph.EdgeCalls, FilePath: filePath, Line: int(c.StartPoint().Row) + 1,
|
||||
}
|
||||
stampFactoryChainReceiver(edge, receiver, resolveChainType(receiver, nil, result))
|
||||
result.Edges = append(result.Edges, edge)
|
||||
}
|
||||
}
|
||||
case "statement":
|
||||
// A statement whose only named child is a bare identifier /
|
||||
// genericDot is a paren-less procedure call (`Baz;`, `Self.Init;`).
|
||||
if callee := pascalParenlessCallee(c, src); callee != "" {
|
||||
e.emitPascalCallEdge(fromID, callee, filePath, int(c.StartPoint().Row)+1, result, procIndex)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *PascalExtractor) emitPascalCallEdge(fromID, callee, filePath string, line int, result *parser.ExtractionResult, procIndex map[string]string) {
|
||||
bare := callee
|
||||
if i := strings.LastIndexByte(bare, '.'); i >= 0 {
|
||||
bare = bare[i+1:]
|
||||
}
|
||||
if bare == "" {
|
||||
return
|
||||
}
|
||||
if target, ok := procIndex[strings.ToLower(bare)]; ok {
|
||||
if target == fromID {
|
||||
return // self-reference noise
|
||||
}
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fromID, To: target, Kind: graph.EdgeCalls, FilePath: filePath, Line: line,
|
||||
Origin: graph.OriginASTResolved,
|
||||
})
|
||||
return
|
||||
}
|
||||
result.Edges = append(result.Edges, &graph.Edge{
|
||||
From: fromID, To: "unresolved::*." + bare, Kind: graph.EdgeCalls, FilePath: filePath, Line: line,
|
||||
Origin: graph.OriginTextMatched,
|
||||
})
|
||||
}
|
||||
|
||||
// --- small helpers -------------------------------------------------------
|
||||
|
||||
// pascalFirstChild returns the first direct child of n with the given type.
|
||||
func pascalFirstChild(n *sitter.Node, typ string) *sitter.Node {
|
||||
for i, _nc := 0, int(n.ChildCount()); i < _nc; i++ {
|
||||
if c := n.Child(i); c.Type() == typ {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pascalChildText returns the trimmed content of n's first child of typ.
|
||||
func pascalChildText(n *sitter.Node, src []byte, typ string) string {
|
||||
if c := pascalFirstChild(n, typ); c != nil {
|
||||
return strings.TrimSpace(c.Content(src))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// pascalTypeBody returns the declClass/declRecord/declEnum/declInterface node a
|
||||
// declType resolves to, unwrapping the intermediate `type` wrapper.
|
||||
func pascalTypeBody(declType *sitter.Node) *sitter.Node {
|
||||
for i, _nc := 0, int(declType.ChildCount()); i < _nc; i++ {
|
||||
c := declType.Child(i)
|
||||
switch c.Type() {
|
||||
case "declClass", "declRecord", "declObject", "declInterface", "declEnum":
|
||||
return c
|
||||
case "type":
|
||||
for j, _nc := 0, int(c.ChildCount()); j < _nc; j++ {
|
||||
if inner := c.Child(j); inner.IsNamed() {
|
||||
return inner
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pascalProcLocalName returns the bare name of a declProc (the identifier; for a
|
||||
// class-body signature there is no genericDot).
|
||||
func pascalProcLocalName(decl *sitter.Node, src []byte) string {
|
||||
if gd := pascalFirstChild(decl, "genericDot"); gd != nil {
|
||||
_, _, bare := splitPascalDotted(strings.TrimSpace(gd.Content(src)))
|
||||
return bare
|
||||
}
|
||||
return pascalChildText(decl, src, "identifier")
|
||||
}
|
||||
|
||||
// pascalProcQualifiedName returns (qualifiedName, owner, bareName) for a
|
||||
// declProc: `TFoo.Bar` → ("TFoo.Bar","TFoo","Bar"); `Hello` → ("Hello","","Hello").
|
||||
func pascalProcQualifiedName(decl *sitter.Node, src []byte) (qualified, owner, bare string) {
|
||||
if gd := pascalFirstChild(decl, "genericDot"); gd != nil {
|
||||
owner, _, bare = splitPascalDotted(strings.TrimSpace(gd.Content(src)))
|
||||
if owner != "" {
|
||||
return owner + "." + bare, owner, bare
|
||||
}
|
||||
return bare, "", bare
|
||||
}
|
||||
bare = pascalChildText(decl, src, "identifier")
|
||||
return bare, "", bare
|
||||
}
|
||||
|
||||
// splitPascalDotted splits `A.B.C` into (owner="A.B", parent="A.B", last="C").
|
||||
func splitPascalDotted(s string) (owner, parent, last string) {
|
||||
if i := strings.LastIndexByte(s, '.'); i >= 0 {
|
||||
return s[:i], s[:i], s[i+1:]
|
||||
}
|
||||
return "", "", s
|
||||
}
|
||||
|
||||
// pascalReturnType returns the declProc's typeref return type (functions only).
|
||||
func pascalReturnType(decl *sitter.Node, src []byte) string {
|
||||
if tr := pascalFirstChild(decl, "typeref"); tr != nil {
|
||||
return strings.TrimSpace(tr.Content(src))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// pascalCallTargetName returns the callee name of an exprCall (its first named
|
||||
// identifier / genericDot child).
|
||||
// pascalDotMethodReceiver splits an exprDot `recv.Method` into the method
|
||||
// name (its trailing identifier) and the receiver text (everything before).
|
||||
func pascalDotMethodReceiver(dot *sitter.Node, src []byte) (method, receiver string) {
|
||||
for i := int(dot.ChildCount()) - 1; i >= 0; i-- {
|
||||
if dot.Child(i).Type() == "identifier" {
|
||||
method = strings.TrimSpace(dot.Child(i).Content(src))
|
||||
break
|
||||
}
|
||||
}
|
||||
if obj := dot.NamedChild(0); obj != nil {
|
||||
receiver = strings.TrimSpace(obj.Content(src))
|
||||
}
|
||||
return method, receiver
|
||||
}
|
||||
|
||||
func pascalCallTargetName(call *sitter.Node, src []byte) string {
|
||||
for i, _nc := 0, int(call.ChildCount()); i < _nc; i++ {
|
||||
c := call.Child(i)
|
||||
if c.Type() == "identifier" || c.Type() == "genericDot" {
|
||||
return strings.TrimSpace(c.Content(src))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// pascalParenlessCallee returns the callee of a paren-less call statement — a
|
||||
// statement whose sole named child is an identifier / genericDot — or "".
|
||||
func pascalParenlessCallee(stmt *sitter.Node, src []byte) string {
|
||||
var named []*sitter.Node
|
||||
for i, _nc := 0, int(stmt.ChildCount()); i < _nc; i++ {
|
||||
if c := stmt.Child(i); c.IsNamed() {
|
||||
named = append(named, c)
|
||||
}
|
||||
}
|
||||
if len(named) != 1 {
|
||||
return ""
|
||||
}
|
||||
if t := named[0].Type(); t == "identifier" || t == "genericDot" {
|
||||
return strings.TrimSpace(named[0].Content(src))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// pascalIndex records a lowercased callable name → id mapping (first writer
|
||||
// wins, so a definition never shadows an earlier class declaration's id).
|
||||
func pascalIndex(idx map[string]string, name, id string) {
|
||||
if name == "" {
|
||||
return
|
||||
}
|
||||
k := strings.ToLower(name)
|
||||
if _, ok := idx[k]; !ok {
|
||||
idx[k] = id
|
||||
}
|
||||
}
|
||||
|
||||
var _ parser.Extractor = (*PascalExtractor)(nil)
|
||||
Reference in New Issue
Block a user