Merge pull request #16 from tgulacsi/cache-regexp

Compile regexps only once (by caching them)
This commit is contained in:
Cheikh Seck authored and GitHub committed 2026-06-06 00:16:38 +00:00
commit 5de7d0efa3
10 files changed
+99 -39

No files matched your search

+10 -10
View File
@@ -15,7 +15,6 @@ import (
"os/exec"
"os/signal"
"path/filepath"
"regexp"
"runtime"
"strconv"
"strings"
@@ -35,6 +34,7 @@ import (
"github.com/cheikh2shift/godex/internal/mcp"
"github.com/cheikh2shift/godex/internal/ml"
"github.com/cheikh2shift/godex/internal/providers"
"github.com/cheikh2shift/godex/internal/rxcache"
"github.com/cheikh2shift/godex/internal/scheduler"
"github.com/cheikh2shift/godex/internal/wizard"
"github.com/cheikh2shift/godex/modelquery"
@@ -1445,8 +1445,8 @@ func handleMCPSubcommand(args []string, configPath string) error {
if err := config.Save(configPath, cfg); err != nil {
return err
}
fmt.Printf("Added MCP server %q to provider %q\n", serverName, provider.Name)
return nil
fmt.Printf("Added MCP server %q to provider %q\n", serverName, provider.Name)
return nil
case "serve":
return handleMCPServeSubcommand(args[1:])
case "remove":
@@ -2852,11 +2852,11 @@ func extractAllToolCalls(text string) []map[string]interface{} {
// Safety fallback for the specific [TOOL_CALL] format
if len(results) == 0 {
toolCallRe := regexp.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
toolCallRe := rxcache.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
tcMatches := toolCallRe.FindAllStringSubmatch(candidate, -1)
for _, m := range tcMatches {
if len(m) > 1 {
nameRe := regexp.MustCompile(`tool\s*=>\s*"([^"]+)"`)
nameRe := rxcache.MustCompile(`tool\s*=>\s*"([^"]+)"`)
if nameMatch := nameRe.FindStringSubmatch(m[1]); len(nameMatch) > 1 {
results = append(results, map[string]interface{}{"name": nameMatch[1], "arguments": map[string]interface{}{}})
}
@@ -2922,7 +2922,7 @@ func extractToolCallsFromJSONArray(text string) []map[string]interface{} {
func extractNativeToolCalls(text string) [][2]string {
var results [][2]string
re := regexp.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
re := rxcache.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
matches := re.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
if len(m) < 4 {
@@ -2977,8 +2977,8 @@ func normalizeToolCallText(text string) string {
nonEmpty := 0
withMargin := 0
marginRe := regexp.MustCompile(`^\s*│`)
stripRe := regexp.MustCompile(`^\s*│\s?`)
marginRe := rxcache.MustCompile(`^\s*│`)
stripRe := rxcache.MustCompile(`^\s*│\s?`)
for _, line := range lines {
if strings.TrimSpace(line) == "" {
@@ -3004,7 +3004,7 @@ func normalizeToolCallText(text string) string {
// Only replaces the first occurrence at the start of the command
func fixDriveLetterPath(cmd string) string {
// Match :/<path> at start and convert to cd /<path>
re := regexp.MustCompile(`(?i)^:\/(.+)`)
re := rxcache.MustCompile(`(?i)^:\/(.+)`)
match := re.FindStringSubmatch(cmd)
if match == nil {
return cmd
@@ -3128,7 +3128,7 @@ func extractJsonObjects(text string) []map[string]interface{} {
}
func extractThinkingText(text string) string {
codeBlockRe := regexp.MustCompile("(?s)```(?:json)?\\n.*?\\n```")
codeBlockRe := rxcache.MustCompile("(?s)```(?:json)?\\n.*?\\n```")
thinking := codeBlockRe.ReplaceAllString(text, "")
thinking = strings.TrimSpace(thinking)
return thinking
+5 -4
View File
@@ -10,11 +10,12 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"sync"
"syscall"
"time"
"github.com/cheikh2shift/godex/internal/rxcache"
)
var runCmdMu sync.Mutex
@@ -178,9 +179,9 @@ type BashServer struct {
}
var (
cdRegex = regexp.MustCompile(`(?i)^cd\s+(\S+)`)
interpreterRegex = regexp.MustCompile(`(?i)(^|\s)(python[0-9.]*|pypy|ruby|perl|python|bash|sh|zsh|fish|r|lua|lua5|tcl|expect)(\s|$)`)
pathRegex = regexp.MustCompile(`(?:^|\s)([/\\]+[^\s/\\]+(?:/[^\s/\\]*)*|[\.]{1,2}[/\\]|(?:~|\$(?:HOME|\w+))[/\\][^\s/\\]+)`)
cdRegex = rxcache.MustCompile(`(?i)^cd\s+(\S+)`)
interpreterRegex = rxcache.MustCompile(`(?i)(^|\s)(python[0-9.]*|pypy|ruby|perl|python|bash|sh|zsh|fish|r|lua|lua5|tcl|expect)(\s|$)`)
pathRegex = rxcache.MustCompile(`(?:^|\s)([/\\]+[^\s/\\]+(?:/[^\s/\\]*)*|[\.]{1,2}[/\\]|(?:~|\$(?:HOME|\w+))[/\\][^\s/\\]+)`)
)
func extractPathsFromCommand(command string) []string {
+4 -3
View File
@@ -13,6 +13,7 @@ import (
"time"
"github.com/cheikh2shift/godex/internal/ml"
"github.com/cheikh2shift/godex/internal/rxcache"
)
func getIntArg(args map[string]interface{}, key, alias string) (float64, bool) {
@@ -603,7 +604,7 @@ func (s *FileSystemServer) searchFileText(args map[string]interface{}) (string,
pattern = "(?i)" + pattern
}
var err error
regex, err = regexp.Compile(pattern)
regex, err = rxcache.Compile(pattern)
if err != nil {
return "", fmt.Errorf("invalid regex pattern: %w", err)
}
@@ -694,7 +695,7 @@ func (s *FileSystemServer) searchInFile(args map[string]interface{}) (string, er
if !caseSensitive {
compiledPattern = "(?i)" + compiledPattern
}
regex, err = regexp.Compile(compiledPattern)
regex, err = rxcache.Compile(compiledPattern)
if err != nil {
return "", fmt.Errorf("invalid regex pattern: %w", err)
}
@@ -758,7 +759,7 @@ func (s *FileSystemServer) searchDirectoryText(args map[string]interface{}) (str
compiledPattern = "(?i)" + compiledPattern
}
var err error
regex, err = regexp.Compile(compiledPattern)
regex, err = rxcache.Compile(compiledPattern)
if err != nil {
return "", fmt.Errorf("invalid regex pattern: %w", err)
}
+2 -2
View File
@@ -6,11 +6,11 @@ import (
"fmt"
"log"
"net/url"
"regexp"
"strings"
"sync"
"time"
"github.com/cheikh2shift/godex/internal/rxcache"
"github.com/chromedp/chromedp"
"golang.org/x/net/html"
)
@@ -204,7 +204,7 @@ func (s *WebScraperServer) processContent(htmlContent, grepPattern string, start
}
func (s *WebScraperServer) applyGrep(htmlContent, pattern string) (string, error) {
re, err := regexp.Compile(pattern)
re, err := rxcache.Compile(pattern)
if err != nil {
return "", fmt.Errorf("invalid grep pattern: %v", err)
}
+2 -2
View File
@@ -13,7 +13,6 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
@@ -22,6 +21,7 @@ import (
"time"
"github.com/cheikh2shift/godex/internal/config"
"github.com/cheikh2shift/godex/internal/rxcache"
)
const (
@@ -294,7 +294,7 @@ func resolveModelPathWithDownload(ctx context.Context, model string, downloadPro
}
quantToFile := make(map[string]string)
quantRe := regexp.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
quantRe := rxcache.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
for _, f := range ggufFiles {
matches := quantRe.FindStringSubmatch(f)
if len(matches) >= 2 {
+4 -3
View File
@@ -6,11 +6,12 @@ import (
"io"
"net/http"
"net/http/httptest"
"regexp"
"strconv"
"strings"
"testing"
"time"
"github.com/cheikh2shift/godex/internal/rxcache"
)
func TestResolveModelPathLocal(t *testing.T) {
@@ -36,7 +37,7 @@ func TestResolveModelPathLocal(t *testing.T) {
}
func TestQuantizationRegexParsing(t *testing.T) {
re := regexp.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
re := rxcache.MustCompile(`-([A-Za-z0-9_]+)\.gguf$`)
tests := []struct {
filename string
@@ -513,7 +514,7 @@ func min(a, b int) int {
}
func TestParseContextValueFromHTML(t *testing.T) {
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
tests := []struct {
html string
+3 -3
View File
@@ -7,13 +7,13 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/cheikh2shift/godex/internal/config"
"github.com/cheikh2shift/godex/internal/rxcache"
)
const (
@@ -169,7 +169,7 @@ func (o *ollamaProvider) fetchOllamaLibraryContext() error {
return err
}
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
matches := re.FindStringSubmatch(string(body))
if len(matches) >= 2 {
contextStr := matches[1]
@@ -203,7 +203,7 @@ func GetOllamaContextLimit(model string) (int, error) {
return 0, err
}
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
matches := re.FindStringSubmatch(string(body))
if len(matches) >= 2 {
contextStr := matches[1]
+7 -6
View File
@@ -5,11 +5,12 @@ import (
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"testing"
"time"
"github.com/cheikh2shift/godex/internal/rxcache"
)
func TestParseOllamaLibraryContextRegex(t *testing.T) {
@@ -69,7 +70,7 @@ func TestParseOllamaLibraryContextRegex(t *testing.T) {
},
}
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -99,7 +100,7 @@ func TestParseOllamaLibraryContextRegex(t *testing.T) {
}
func TestExtractContextFromOllamaHTML(t *testing.T) {
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
realisticHTML := `
<!DOCTYPE html>
@@ -176,7 +177,7 @@ func TestExtractContextFromOllamaHTMLVariations(t *testing.T) {
},
}
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
for _, v := range variations {
t.Run(v.name, func(t *testing.T) {
@@ -205,7 +206,7 @@ func TestExtractContextNoMatch(t *testing.T) {
`<div>just numbers 128K here</div>`,
}
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
for _, html := range noMatchCases {
matches := re.FindStringSubmatch(html)
@@ -285,7 +286,7 @@ func TestFetchOllamaLibraryContextIntegration(t *testing.T) {
}
client := &http.Client{Timeout: 30 * time.Second}
re := regexp.MustCompile(`(\d+)[Kk]\s*context\s*window`)
re := rxcache.MustCompile(`(\d+)[Kk]\s*context\s*window`)
for _, tt := range tests {
t.Run(tt.model, func(t *testing.T) {
+55
View File
@@ -0,0 +1,55 @@
package rxcache
import (
"regexp"
"sync"
)
func MustCompile(pattern string) *regexp.Regexp { return globalCache.MustCompile(pattern) }
func Compile(pattern string) (*regexp.Regexp, error) { return globalCache.Compile(pattern) }
const globalCacheLimit = 1000
var globalCache = RegexpCache{m: make(map[string]*regexp.Regexp, globalCacheLimit), limit: globalCacheLimit}
type RegexpCache struct {
mu sync.RWMutex
m map[string]*regexp.Regexp
limit int
}
func (c *RegexpCache) MustCompile(pattern string) *regexp.Regexp {
r, err := c.Compile(pattern)
if err != nil {
panic(err)
}
return r
}
func (c *RegexpCache) Compile(pattern string) (*regexp.Regexp, error) {
c.mu.RLock()
r := c.m[pattern]
c.mu.RUnlock()
if r != nil {
return r, nil
}
c.mu.Lock()
defer c.mu.Unlock()
if r = c.m[pattern]; r != nil {
return r, nil
}
if n := len(c.m) - c.limit; c.limit > 0 && n > 0 {
for k := range c.m {
delete(c.m, k)
n--
if n <= 0 {
break
}
}
}
r, err := regexp.Compile(pattern)
if r != nil {
c.m[pattern] = r
}
return r, err
}
+7 -6
View File
@@ -2,8 +2,9 @@ package toolcalls
import (
"encoding/json"
"regexp"
"strings"
"github.com/cheikh2shift/godex/internal/rxcache"
)
// ExtractAllToolCalls extracts tool calls from a model response in a variety of common formats.
@@ -69,11 +70,11 @@ func ExtractAllToolCalls(text string) []map[string]interface{} {
// Safety fallback for the specific [TOOL_CALL] format
if len(results) == 0 {
toolCallRe := regexp.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
toolCallRe := rxcache.MustCompile(`(?s)\[TOOL_CALL\]\s*(.*?)\s*\[/TOOL_CALL\]`)
tcMatches := toolCallRe.FindAllStringSubmatch(candidate, -1)
for _, m := range tcMatches {
if len(m) > 1 {
nameRe := regexp.MustCompile(`tool\s*=>\s*"([^"]+)"`)
nameRe := rxcache.MustCompile(`tool\s*=>\s*"([^"]+)"`)
if nameMatch := nameRe.FindStringSubmatch(m[1]); len(nameMatch) > 1 {
results = append(results, map[string]interface{}{"name": nameMatch[1], "arguments": map[string]interface{}{}})
}
@@ -139,7 +140,7 @@ func extractToolCallsFromJSONArray(text string) []map[string]interface{} {
func extractNativeToolCalls(text string) [][2]string {
var results [][2]string
re := regexp.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
re := rxcache.MustCompile(`\[TOOL_CALL:\s*([^\s|]+)\s*\|`)
matches := re.FindAllStringSubmatchIndex(text, -1)
for _, m := range matches {
if len(m) < 4 {
@@ -194,8 +195,8 @@ func normalizeToolCallText(text string) string {
nonEmpty := 0
withMargin := 0
marginRe := regexp.MustCompile(`^\s*│`)
stripRe := regexp.MustCompile(`^\s*│\s?`)
marginRe := rxcache.MustCompile(`^\s*│`)
stripRe := rxcache.MustCompile(`^\s*│\s?`)
for _, line := range lines {
if strings.TrimSpace(line) == "" {