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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:33:42 +08:00
commit a06f331eb8
3186 changed files with 689843 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
package languages
import (
"regexp"
"strings"
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/parser"
)
// AL is Microsoft's Business Central / Dynamics 365 language. The unit
// of code is the "object" — `table 50000 MyTable` / `page Id Name` /
// `codeunit` / `report` / `query` / `xmlport` / `enum` — each owning
// a body of procedures, triggers, and fields. We model the object
// itself as a type node and its procedures / triggers as methods.
var (
alObjectRe = regexp.MustCompile(`(?mi)^\s*(table|page|codeunit|report|query|xmlport|enum|enumextension|pageextension|tableextension|profile|permissionset|controladdin|interface)\s+\d*\s*"?([A-Za-z][\w ]*)"?`)
alProcRe = regexp.MustCompile(`(?mi)^\s*(local\s+|internal\s+|protected\s+)?procedure\s+"?([A-Za-z][\w ]*)"?\s*\(`)
alTrigRe = regexp.MustCompile(`(?mi)^\s*trigger\s+(On[A-Za-z]\w*)\s*\(`)
alVarRe = regexp.MustCompile(`(?mi)^\s*"?([A-Za-z][\w ]*)"?\s*:\s*(Record|Text|Integer|Decimal|Boolean|Date|Time|DateTime|Code|Option|Enum)\b`)
alUsing = regexp.MustCompile(`(?mi)^\s*using\s+([A-Za-z][\w.]*)`)
alCallRe = regexp.MustCompile(`\b([A-Za-z_][\w]*)\s*\(`)
)
// ALExtractor extracts Microsoft AL source files using regex.
type ALExtractor struct{}
func NewALExtractor() *ALExtractor { return &ALExtractor{} }
func (e *ALExtractor) Language() string { return "al" }
func (e *ALExtractor) Extensions() []string { return []string{".al", ".dal"} }
func (e *ALExtractor) Extract(filePath string, src []byte) (*parser.ExtractionResult, error) {
lines := strings.Split(string(src), "\n")
result := &parser.ExtractionResult{}
fileNode := &graph.Node{
ID: filePath, Kind: graph.KindFile, Name: filePath,
FilePath: filePath, StartLine: 1, EndLine: len(lines),
Language: "al",
}
result.Nodes = append(result.Nodes, fileNode)
seen := make(map[string]bool)
for _, m := range alObjectRe.FindAllSubmatchIndex(src, -1) {
keyword := strings.ToLower(string(src[m[2]:m[3]]))
name := strings.TrimSpace(string(src[m[4]:m[5]]))
if name == "" {
continue
}
kind := graph.KindType
if keyword == "interface" {
kind = graph.KindInterface
}
line := lineAt(src, m[0])
endLine := findBlockEnd(lines, line)
id := filePath + "::" + name
if seen[id] {
continue
}
seen[id] = true
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: kind, Name: name,
FilePath: filePath, StartLine: line, EndLine: endLine,
Language: "al",
Meta: map[string]any{"al_kind": keyword},
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileNode.ID, To: id, Kind: graph.EdgeDefines,
FilePath: filePath, Line: line,
})
}
for _, re := range []*regexp.Regexp{alProcRe, alTrigRe} {
for _, m := range re.FindAllSubmatchIndex(src, -1) {
nameIdx := m[len(m)-2:]
name := strings.TrimSpace(string(src[nameIdx[0]:nameIdx[1]]))
line := lineAt(src, m[0])
endLine := findBlockEnd(lines, line)
id := filePath + "::" + name
if seen[id] {
continue
}
seen[id] = true
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindMethod, Name: name,
FilePath: filePath, StartLine: line, EndLine: endLine,
Language: "al",
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileNode.ID, To: id, Kind: graph.EdgeDefines,
FilePath: filePath, Line: line,
})
}
}
for _, m := range alVarRe.FindAllSubmatchIndex(src, -1) {
name := strings.TrimSpace(string(src[m[2]:m[3]]))
if isALKeyword(strings.ToLower(name)) || name == "" {
continue
}
line := lineAt(src, m[0])
id := filePath + "::" + name
if seen[id] {
continue
}
seen[id] = true
result.Nodes = append(result.Nodes, &graph.Node{
ID: id, Kind: graph.KindVariable, Name: name,
FilePath: filePath, StartLine: line, EndLine: line,
Language: "al",
})
result.Edges = append(result.Edges, &graph.Edge{
From: fileNode.ID, To: id, Kind: graph.EdgeDefines,
FilePath: filePath, Line: line,
})
}
for _, m := range alUsing.FindAllSubmatchIndex(src, -1) {
ns := strings.TrimSpace(string(src[m[2]:m[3]]))
line := lineAt(src, m[0])
result.Edges = append(result.Edges, &graph.Edge{
From: fileNode.ID, To: "unresolved::import::" + ns,
Kind: graph.EdgeImports, FilePath: filePath, Line: line,
})
}
funcRanges := buildFuncRanges(result)
for _, m := range alCallRe.FindAllSubmatchIndex(src, -1) {
name := string(src[m[2]:m[3]])
if isALKeyword(strings.ToLower(name)) {
continue
}
line := lineAt(src, m[0])
callerID := findEnclosingFunc(funcRanges, line)
if callerID == "" || strings.HasSuffix(callerID, "::"+name) {
continue
}
result.Edges = append(result.Edges, &graph.Edge{
From: callerID, To: "unresolved::" + name,
Kind: graph.EdgeCalls, FilePath: filePath, Line: line,
})
}
return result, nil
}
func isALKeyword(s string) bool {
switch s {
case "if", "then", "else", "case", "of", "begin", "end", "for", "while",
"repeat", "until", "do", "var", "procedure", "trigger", "local",
"internal", "protected", "return", "exit", "with", "using", "true",
"false", "and", "or", "not", "mod", "div", "in", "is", "record",
"text", "integer", "decimal", "boolean", "date", "time", "datetime",
"code", "option", "enum":
return true
}
return false
}
var _ parser.Extractor = (*ALExtractor)(nil)