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

118 lines
3.7 KiB
Go

package languages
import (
"path/filepath"
"strings"
"unsafe"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/config"
"github.com/zzet/gortex/internal/parser"
"github.com/zzet/gortex/internal/parser/forest"
)
// defaultGrammarSymbol derives the tree-sitter C entry-point symbol
// for a language: `tree_sitter_<language>` with every non-alphanumeric
// character folded to an underscore — the convention upstream grammars
// follow (tree-sitter-c-sharp exports `tree_sitter_c_sharp`).
func defaultGrammarSymbol(language string) string {
var b strings.Builder
b.WriteString("tree_sitter_")
for _, r := range strings.ToLower(language) {
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') {
b.WriteRune(r)
} else {
b.WriteByte('_')
}
}
return b.String()
}
// normalizeGrammarExtensions trims each extension and ensures a
// leading dot, dropping blanks. Case is preserved — the registry's
// extension map is case-sensitive (it distinguishes .R from .r).
func normalizeGrammarExtensions(exts []string) []string {
out := make([]string, 0, len(exts))
for _, e := range exts {
e = strings.TrimSpace(e)
if e == "" {
continue
}
if !strings.HasPrefix(e, ".") {
e = "." + e
}
out = append(out, e)
}
return out
}
// firstClaimedExtension returns the first extension already mapped to
// a registered extractor, or "" when every extension is free.
func firstClaimedExtension(reg *parser.Registry, exts []string) string {
for _, e := range exts {
if _, exists := reg.GetByExtension(e); exists {
return e
}
}
return ""
}
// RegisterCustomGrammars loads every user-declared tree-sitter grammar
// and registers it as a signature-only forest extractor. A grammar
// whose name or any extension collides with a built-in extractor is
// skipped — built-in depth wins — as is one that fails to validate or
// load, each with a logged warning so a typo or a missing library
// never aborts startup. A relative library path resolves against the
// working directory.
func RegisterCustomGrammars(reg *parser.Registry, specs []config.GrammarSpec, log *zap.Logger) {
if reg == nil {
return
}
if log == nil {
log = zap.NewNop()
}
for _, spec := range specs {
lang := strings.TrimSpace(spec.Language)
exts := normalizeGrammarExtensions(spec.Extensions)
if lang == "" || strings.TrimSpace(spec.Library) == "" || len(exts) == 0 {
log.Warn("custom grammar: skipped — language, library and extensions are all required",
zap.String("language", spec.Language), zap.String("library", spec.Library))
continue
}
if _, exists := reg.GetByLanguage(lang); exists {
log.Warn("custom grammar: skipped — language already registered by a built-in extractor",
zap.String("language", lang))
continue
}
if conflict := firstClaimedExtension(reg, exts); conflict != "" {
log.Warn("custom grammar: skipped — extension already claimed by a built-in extractor",
zap.String("language", lang), zap.String("extension", conflict))
continue
}
libPath := strings.TrimSpace(spec.Library)
if !filepath.IsAbs(libPath) {
if abs, err := filepath.Abs(libPath); err == nil {
libPath = abs
}
}
symbol := strings.TrimSpace(spec.Symbol)
if symbol == "" {
symbol = defaultGrammarSymbol(lang)
}
langPtr, err := loadGrammarLanguage(libPath, symbol)
if err != nil {
log.Warn("custom grammar: failed to load — skipped",
zap.String("language", lang), zap.String("library", libPath),
zap.String("symbol", symbol), zap.Error(err))
continue
}
getLang := func() unsafe.Pointer { return langPtr }
reg.Register(forest.New(lang, exts, getLang, nil))
log.Info("custom grammar registered",
zap.String("language", lang), zap.Strings("extensions", exts))
}
}