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

113 lines
4.1 KiB
Go

package languages
import (
"regexp"
"strings"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
)
var (
dfmObjectRe = regexp.MustCompile(`(?i)^\s*(?:object|inherited|inline)\s+(\w+)\s*:\s*(\w+)`)
dfmEventRe = regexp.MustCompile(`(?i)^\s*(On\w+)\s*=\s*(\w+)\s*$`)
dfmEndRe = regexp.MustCompile(`(?i)^\s*end\b`)
// dfmListOpenRe / dfmBinOpenRe match a property whose value continues onto
// later lines: a `( … )` set/collection or a `{ … }` binary-data blob. Those
// lines carry arbitrary text (including literal `object`/`end` tokens) that
// must not be parsed as structure.
dfmListOpenRe = regexp.MustCompile(`=\s*\(\s*$`)
dfmBinOpenRe = regexp.MustCompile(`=\s*\{\s*$`)
)
// DFMExtractor extracts Delphi / FireMonkey form-definition files (.dfm, .fmx).
// Each `object Name: TType` declaration becomes a node — the top-level form is a
// type, nested controls are fields of it — with a reference to the component
// class TType, and each `OnEvent = Handler` assignment references the Pascal
// event-handler method, so a form is linked to the component types and unit
// methods it uses. The Language() key is distinct so it cannot clobber the
// Pascal extractor; the emitted nodes are labelled `pascal` to group with Delphi.
type DFMExtractor struct{}
// NewDFMExtractor constructs a Delphi form-definition extractor.
func NewDFMExtractor() *DFMExtractor { return &DFMExtractor{} }
func (e *DFMExtractor) Language() string { return "dfm" }
func (e *DFMExtractor) Extensions() []string { return []string{".dfm", ".fmx"} }
func (e *DFMExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
result := &parser.ExtractionResult{}
lines := strings.Split(string(src), "\n")
fileNode := &graph.Node{
ID: filePath, Kind: graph.KindFile, Name: filePath,
FilePath: filePath, StartLine: 1, EndLine: len(lines), Language: "pascal",
}
result.Nodes = append(result.Nodes, fileNode)
// stack holds the node ID of each currently-open object, innermost last, so
// a nested control attaches to its IMMEDIATE parent (true tree nesting), not
// the form root. closer is non-empty while inside a multi-line property
// value (")" for a set, "}" for binary data) whose lines must be skipped.
var stack []string
seen := map[string]bool{}
closer := ""
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if closer != "" {
if strings.HasSuffix(trimmed, closer) {
closer = ""
}
continue
}
switch {
case dfmObjectRe.MatchString(line):
m := dfmObjectRe.FindStringSubmatch(line)
name, typ := m[1], m[2]
id := filePath + "::" + name
parent := ""
if len(stack) > 0 {
parent = stack[len(stack)-1]
}
if !seen[id] {
seen[id] = true
kind := graph.KindField
if len(stack) == 0 {
kind = graph.KindType // the top-level form/frame
}
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: kind, Name: name,
FilePath: filePath, StartLine: i + 1, EndLine: i + 1, Language: "pascal",
Meta: map[string]any{"dfm_type": typ},
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileNode.ID, To: id, Kind: graph.EdgeDefines, FilePath: filePath, Line: i + 1,
})
if parent != "" {
// MemberOf the immediate parent control, not the form root.
result.Edges = append(result.Edges, &graph.Edge{
From: id, To: parent, Kind: graph.EdgeMemberOf, FilePath: filePath, Line: i + 1,
})
}
result.Edges = append(result.Edges, &graph.Edge{
From: id, To: "unresolved::" + typ, Kind: graph.EdgeReferences, FilePath: filePath, Line: i + 1,
})
}
stack = append(stack, id)
case dfmListOpenRe.MatchString(line):
closer = ")"
case dfmBinOpenRe.MatchString(line):
closer = "}"
case dfmEventRe.MatchString(line):
m := dfmEventRe.FindStringSubmatch(line)
result.Edges = append(result.Edges, &graph.Edge{
From: fileNode.ID, To: "unresolved::" + m[2], Kind: graph.EdgeReferences, FilePath: filePath, Line: i + 1,
})
case dfmEndRe.MatchString(line):
if len(stack) > 0 {
stack = stack[:len(stack)-1]
}
}
}
return result, nil
}