chore: modernize CI and update Go toolchain

- Bump Go from 1.19 to 1.26 and update all dependencies
- Rewrite CI workflow with matrix strategy (Linux, macOS, Windows)
- Update GitHub Actions to current versions (checkout@v4, setup-go@v5)
- Update CodeQL actions from v1 to v3
- Fix cross-platform bug in mock/path.go (path.Join -> filepath.Join)
- Clean up dependabot config (weekly schedule, remove stale ignore)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Allen Lane
2026-02-14 20:58:51 -05:00
parent cc85a4bdb1
commit 2a19755804
657 changed files with 49050 additions and 32001 deletions

View File

@@ -4,10 +4,17 @@ Regexp2 is a feature-rich RegExp engine for Go. It doesn't have constant time g
## Basis of the engine
The engine is ported from the .NET framework's System.Text.RegularExpressions.Regex engine. That engine was open sourced in 2015 under the MIT license. There are some fundamental differences between .NET strings and Go strings that required a bit of borrowing from the Go framework regex engine as well. I cleaned up a couple of the dirtier bits during the port (regexcharclass.cs was terrible), but the parse tree, code emmitted, and therefore patterns matched should be identical.
## New Code Generation
For extra performance use `regexp2` with [`regexp2cg`](https://github.com/dlclark/regexp2cg). It is a code generation utility for `regexp2` and you can likely improve your regexp runtime performance by 3-10x in hot code paths. As always you should benchmark your specifics to confirm the results. Give it a try!
## Installing
This is a go-gettable library, so install is easy:
go get github.com/dlclark/regexp2/...
go get github.com/dlclark/regexp2
To use the new Code Generation (while it's in beta) you'll need to use the `code_gen` branch:
go get github.com/dlclark/regexp2@code_gen
## Usage
Usage is similar to the Go `regexp` package. Just like in `regexp`, you start by converting a regex into a state machine via the `Compile` or `MustCompile` methods. They ultimately do the same thing, but `MustCompile` will panic if the regex is invalid. You can then use the provided `Regexp` struct to find matches repeatedly. A `Regexp` struct is safe to use across goroutines.

View File

@@ -50,8 +50,20 @@ func makeDeadline(d time.Duration) fasttime {
// Start or extend clock if necessary.
if end > fast.clockEnd.read() {
// If time.Since(last use) > timeout, there's a chance that
// fast.current will no longer be updated, which can lead to
// incorrect 'end' calculations that can trigger a false timeout
fast.mu.Lock()
if !fast.running && !fast.start.IsZero() {
// update fast.current
fast.current.write(durationToTicks(time.Since(fast.start)))
// recalculate our end value
end = fast.current.read() + durationToTicks(d+clockPeriod)
}
fast.mu.Unlock()
extendClock(end)
}
return end
}

View File

@@ -6,8 +6,9 @@ import (
)
// Match is a single regex result match that contains groups and repeated captures
// -Groups
// -Capture
//
// -Groups
// -Capture
type Match struct {
Group //embeded group 0
@@ -43,10 +44,10 @@ type Group struct {
type Capture struct {
// the original string
text []rune
// the position in the original string where the first character of
// captured substring was found.
// Index is the position in the underlying rune slice where the first character of
// captured substring was found. Even if you pass in a string this will be in Runes.
Index int
// the length of the captured substring.
// Length is the number of runes in the captured substring.
Length int
}
@@ -187,7 +188,8 @@ func (m *Match) addMatch(c, start, l int) {
}
// Nonpublic builder: Add a capture to balance the specified group. This is used by the
// balanced match construct. (?<foo-foo2>...)
//
// balanced match construct. (?<foo-foo2>...)
//
// If there were no such thing as backtracking, this would be as simple as calling RemoveMatch(c).
// However, since we have backtracking, we need to keep track of everything.

View File

@@ -18,8 +18,12 @@ import (
"github.com/dlclark/regexp2/syntax"
)
// Default timeout used when running regexp matches -- "forever"
var DefaultMatchTimeout = time.Duration(math.MaxInt64)
var (
// DefaultMatchTimeout used when running regexp matches -- "forever"
DefaultMatchTimeout = time.Duration(math.MaxInt64)
// DefaultUnmarshalOptions used when unmarshaling a regex from text
DefaultUnmarshalOptions = None
)
// Regexp is the representation of a compiled regular expression.
// A Regexp is safe for concurrent use by multiple goroutines.
@@ -43,7 +47,7 @@ type Regexp struct {
code *syntax.Code // compiled program
// cache of machines for running regexp
muRun sync.Mutex
muRun *sync.Mutex
runner []*runner
}
@@ -72,6 +76,7 @@ func Compile(expr string, opt RegexOptions) (*Regexp, error) {
capsize: code.Capsize,
code: code,
MatchTimeout: DefaultMatchTimeout,
muRun: &sync.Mutex{},
}, nil
}
@@ -371,3 +376,20 @@ func (re *Regexp) GroupNumberFromName(name string) int {
return -1
}
// MarshalText implements [encoding.TextMarshaler]. The output
// matches that of calling the [Regexp.String] method.
func (re *Regexp) MarshalText() ([]byte, error) {
return []byte(re.String()), nil
}
// UnmarshalText implements [encoding.TextUnmarshaler] by calling
// [Compile] on the encoded value.
func (re *Regexp) UnmarshalText(text []byte) error {
newRE, err := Compile(string(text), DefaultUnmarshalOptions)
if err != nil {
return err
}
*re = *newRE
return nil
}

View File

@@ -1604,6 +1604,10 @@ func (re *Regexp) getRunner() *runner {
// run using re. (The cache empties when re gets garbage collected.)
func (re *Regexp) putRunner(r *runner) {
re.muRun.Lock()
r.runtext = nil
if r.runmatch != nil {
r.runmatch.text = nil
}
re.runner = append(re.runner, r)
re.muRun.Unlock()
}

View File

@@ -553,10 +553,10 @@ func (p *parser) scanRegex() (*regexNode, error) {
}
case '.':
if p.useOptionE() {
p.addUnitSet(ECMAAnyClass())
} else if p.useOptionS() {
if p.useOptionS() {
p.addUnitSet(AnyClass())
} else if p.useOptionE() {
p.addUnitSet(ECMAAnyClass())
} else {
p.addUnitNotone('\n')
}