From 8ad5c0d060fdc8df6ecc54252a86d7c5d968970b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Tue, 10 Dec 2024 16:35:49 +0800 Subject: [PATCH 01/14] Add re2 multi-mode matching --- all_test.go | 82 ++++++++++++++++++++++++ buildtools/wasm/Dockerfile | 6 ++ example_test.go | 17 +++++ internal/cre2/cre2.go | 30 +++++++++ internal/re2.go | 80 +++++++++++++++++++++++ internal/re2_re2_cgo.go | 47 +++++++++++++- internal/re2_wazero.go | 126 +++++++++++++++++++++++++++++++++++-- internal/wasm/libcre2.wasm | Bin 3255408 -> 3270506 bytes re2.go | 8 +++ 9 files changed, 388 insertions(+), 8 deletions(-) diff --git a/all_test.go b/all_test.go index 3d71ea5..46fb73e 100644 --- a/all_test.go +++ b/all_test.go @@ -47,6 +47,20 @@ var badRe = []stringError{ {strings.Repeat(`\pL`, 27000), "expression too large"}, } +var badSet = []stringError{ + {`*`, "no argument for repetition operator: *"}, + {`+`, "no argument for repetition operator: +"}, + {`?`, "no argument for repetition operator: ?"}, + {`(abc`, "missing ): (abc"}, + {`abc)`, "unexpected ): abc)"}, + {`x[a-z`, "missing ]: [a-z"}, + {`[z-a]`, "invalid character class range: z-a"}, + {`abc\`, "trailing \\"}, + {`a**`, "bad repetition operator: **"}, + {`a*+`, "bad repetition operator: *+"}, + {`\x`, "invalid escape sequence: \\x"}, +} + func compileTest(t *testing.T, expr string, error string) *Regexp { re, err := Compile(expr) if error == "" && err != nil { @@ -72,6 +86,29 @@ func TestBadCompile(t *testing.T) { } } +func TestGoodSetCompile(t *testing.T) { + compileSetTest(t, goodRe, "") +} + +func compileSetTest(t *testing.T, exprs []string, error string) *Set { + set, err := CompileSet(exprs) + if error == "" && err != nil { + t.Error("compiling `", exprs, "`; unexpected error: ", err.Error()) + } + if error != "" && err == nil { + t.Error("compiling `", exprs, "`; missing error") + } else if error != "" && !strings.Contains(err.Error(), error) { + t.Error("compiling `", exprs, "`; wrong error: ", err.Error(), "; want ", error) + } + return set +} + +func TestBadCompileSet(t *testing.T) { + for i := 0; i < len(badSet); i++ { + compileSetTest(t, []string{badSet[i].re}, badSet[i].err) + } +} + func matchTest(t *testing.T, test *FindTest) { re := compileTest(t, test.pat, "") if re == nil { @@ -94,6 +131,51 @@ func TestMatch(t *testing.T) { } } +type SetTest struct { + expr []string + matches map[string][]int +} + +var setTests = []SetTest{ + { + expr: []string{"abc", "\\d+"}, + matches: map[string][]int{ + "abc": {0}, + "123": {1}, + "abc123": {0, 1}, + "def": {}, + }, + }, + { + expr: []string{"[a-c]+", "(d)(e){0}(f)"}, + matches: map[string][]int{ + "a234v": {0}, + "df": {1}, + "abcdf": {0, 1}, + "def": {}, + }, + }, +} + +func setMatchTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { + m := set.Match([]byte(matchStr), 10) + if !reflect.DeepEqual(m, matchedIds) { + t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) + } +} + +func TestSetMatch(t *testing.T) { + for _, test := range setTests { + set := compileSetTest(t, test.expr, "") + if set == nil { + return + } + for matchStr, matchedIds := range test.matches { + setMatchTest(t, set, matchStr, matchedIds) + } + } +} + func matchFunctionTest(t *testing.T, test *FindTest) { m, err := MatchString(test.pat, test.text) if err == nil { diff --git a/buildtools/wasm/Dockerfile b/buildtools/wasm/Dockerfile index 851490c..2542a01 100644 --- a/buildtools/wasm/Dockerfile +++ b/buildtools/wasm/Dockerfile @@ -57,6 +57,12 @@ RUN $CXX -o libcre2-noopt.so -Wl,--global-base=1024 $LDFLAGS \ -Wl,--export=cre2_named_groups_iter_new \ -Wl,--export=cre2_named_groups_iter_next \ -Wl,--export=cre2_named_groups_iter_delete \ + -Wl,--export=cre2_set_new \ + -Wl,--export=cre2_set_add_simple \ + -Wl,--export=cre2_set_add \ + -Wl,--export=cre2_set_match \ + -Wl,--export=cre2_set_delete \ + -Wl,--export=cre2_set_compile \ -Wl,--export=__wasm_init_tls \ -Wl,--export=__stack_pointer \ -Wl,--export=__tls_base diff --git a/example_test.go b/example_test.go index 235db6f..b3a3a97 100644 --- a/example_test.go +++ b/example_test.go @@ -426,3 +426,20 @@ func ExampleRegexp_FindAllIndex() { // [[1 3]] // [[1 3] [4 6]] } + +func ExampleCompileSet() { + exprs := []string{"abc", "\\d+"} + set, err := regexp.CompileSet(exprs) + if err != nil { + panic(err) + } + fmt.Println(set.Match([]byte("abcd"), len(exprs))) + fmt.Println(set.Match([]byte("123"), len(exprs))) + fmt.Println(set.Match([]byte("abc123"), len(exprs))) + fmt.Println(set.Match([]byte("def"), len(exprs))) + // Output: + // [0] + // [1] + // [0 1] + // [] +} diff --git a/internal/cre2/cre2.go b/internal/cre2/cre2.go index 3a0fe94..ef3a4db 100644 --- a/internal/cre2/cre2.go +++ b/internal/cre2/cre2.go @@ -26,6 +26,12 @@ void cre2_opt_set_posix_syntax(void* opt, int flag); void cre2_opt_set_case_sensitive(void* opt, int flag); void cre2_opt_set_latin1_encoding(void* opt); void cre2_opt_set_max_mem(void* opt, int64_t size); +void* cre2_set_new(void* opt, int anchor); +int cre2_set_add_simple(void* set, void* pattern); +int cre2_set_add(void* set, void* pattern, int pattern_len, void* errors, int errors_len); +int cre2_set_compile(void* set); +int cre2_set_match(void* set, void* text, int text_len, void* match, int nmatch); +void cre2_set_delete(void* set); void* malloc(size_t size); void free(void* ptr); @@ -88,6 +94,30 @@ func DeleteOpt(opt unsafe.Pointer) { C.cre2_opt_delete(opt) } +func NewSet(opt unsafe.Pointer, anchor int) unsafe.Pointer { + return C.cre2_set_new(opt, C.int(anchor)) +} + +func SetAddSimple(set unsafe.Pointer, patternPtr unsafe.Pointer) int { + return int(C.cre2_set_add_simple(set, patternPtr)) +} + +func SetAdd(set unsafe.Pointer, patternPtr unsafe.Pointer, patternLen int, errors unsafe.Pointer, errorLen int) int { + return int(C.cre2_set_add(set, patternPtr, C.int(patternLen), errors, C.int(errorLen))) +} + +func SetCompile(set unsafe.Pointer) int { + return int(C.cre2_set_compile(set)) +} + +func SetMatch(set unsafe.Pointer, textPtr unsafe.Pointer, textLen int, match unsafe.Pointer, nMatch int) int { + return int(C.cre2_set_match(set, textPtr, C.int(textLen), match, C.int(nMatch))) +} + +func SetDelete(ptr unsafe.Pointer) { + C.cre2_set_delete(ptr) +} + func OptSetLogErrors(opt unsafe.Pointer, flag bool) { C.cre2_opt_set_log_errors(opt, cFlag(flag)) } diff --git a/internal/re2.go b/internal/re2.go index 4631556..4515b1f 100644 --- a/internal/re2.go +++ b/internal/re2.go @@ -2,6 +2,8 @@ package internal import ( "bytes" + "encoding/binary" + "errors" "fmt" "runtime" "strconv" @@ -16,6 +18,13 @@ import ( // https://github.com/golang/go/blob/master/src/regexp/syntax/parse.go#L95 const maxSize = 128 << 20 +type Set struct { + ptr wasmPtr + abi *libre2ABI + opts CompileOptions + released uint32 +} + type Regexp struct { ptr wasmPtr @@ -57,6 +66,77 @@ type CompileOptions struct { Latin1 bool } +const errorBufferLength = 64 + +func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { + abi := newABI() + setPtr := newSet(abi, opts) + set := &Set{ + ptr: setPtr, + abi: abi, + opts: opts, + } + estimatedMemorySize := errorBufferLength + for _, expr := range exprs { + estimatedMemorySize += len(expr) + 2 + 8 + } + alloc := abi.startOperation(estimatedMemorySize) + defer abi.endOperation(alloc) + + errorBuffer := alloc.newCStringArray(1) + for _, expr := range exprs { + cs := alloc.newCString(expr) + res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength) + if res == -1 { + errorMessage := readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength) + return nil, errors.New(errorMessage) + } + } + setCompile(set) + // Use func(interface{}) form for nottinygc compatibility. + runtime.SetFinalizer(set, func(obj interface{}) { + obj.(*Set).release() + }) + return set, nil +} + +func readErrorMessage(alloc *allocation, ptr wasmPtr, length int) string { + errMsgBytes := alloc.read(ptr, length) + nulIndex := bytes.IndexByte(errMsgBytes, 0) + if nulIndex != -1 { + errMsgBytes = errMsgBytes[:nulIndex] + } + return string(errMsgBytes) +} + +func (set *Set) release() { + if !atomic.CompareAndSwapUint32(&set.released, 0, 1) { + return + } + deleteSet(set.abi, set.ptr) +} + +func (set *Set) Match(b []byte, n int) []int { + alloc := set.abi.startOperation(len(b) + 8 + n*8) + defer set.abi.endOperation(alloc) + + matchArr := alloc.newCStringArray(n) + defer matchArr.free() + + cs := alloc.newCStringFromBytes(b) + matchedCount := setMatch(set, cs, matchArr.ptr, n) + matchedIDs := make([]int, min(matchedCount, n)) + matches := alloc.read(matchArr.ptr, n*4) + for i := 0; i < len(matchedIDs); i++ { + matchedIDs[i] = int(binary.LittleEndian.Uint32(matches[i*4:])) + } + + runtime.KeepAlive(b) + runtime.KeepAlive(matchArr) + runtime.KeepAlive(set) // don't allow finalizer to run during method + return matchedIDs +} + func Compile(expr string, opts CompileOptions) (*Regexp, error) { abi := newABI() alloc := abi.startOperation(len(expr) + 2 + 8) diff --git a/internal/re2_re2_cgo.go b/internal/re2_re2_cgo.go index 4fa9fcf..b98edc5 100644 --- a/internal/re2_re2_cgo.go +++ b/internal/re2_re2_cgo.go @@ -3,9 +3,8 @@ package internal import ( - "unsafe" - "github.com/wasilibs/go-re2/internal/cre2" + "unsafe" ) type wasmPtr unsafe.Pointer @@ -114,6 +113,10 @@ func (a *allocation) newCStringArray(n int) cStringArray { return cStringArray{ptr: wasmPtr(ptr)} } +func (a *allocation) read(ptr wasmPtr, size int) []byte { + return (*[1 << 30]byte)(unsafe.Pointer(ptr))[:size:size] +} + type cString struct { ptr unsafe.Pointer length int @@ -166,3 +169,43 @@ func readMatches(alloc *allocation, cs cString, matchesPtr wasmPtr, n int, deliv } } } + +func newSet(_ *libre2ABI, opts CompileOptions) wasmPtr { + opt := cre2.NewOpt() + defer cre2.DeleteOpt(opt) + cre2.OptSetMaxMem(opt, maxSize) + cre2.OptSetLogErrors(opt, false) + if opts.Longest { + cre2.OptSetLongestMatch(opt, true) + } + if opts.Posix { + cre2.OptSetPosixSyntax(opt, true) + } + if opts.CaseInsensitive { + cre2.OptSetCaseSensitive(opt, false) + } + if opts.Latin1 { + cre2.OptSetLatin1Encoding(opt) + } + return wasmPtr(cre2.NewSet(opt, 0)) +} + +func setAddSimple(set *Set, s cString) int32 { + return int32(cre2.SetAddSimple(unsafe.Pointer(set.ptr), s.ptr)) +} + +func setAdd(set *Set, s cString, errors wasmPtr, errorsLen int) int32 { + return int32(cre2.SetAdd(unsafe.Pointer(set.ptr), s.ptr, s.length, unsafe.Pointer(errors), errorsLen)) +} + +func setCompile(set *Set) int32 { + return int32(cre2.SetCompile(unsafe.Pointer(set.ptr))) +} + +func setMatch(set *Set, cs cString, matchedPtr wasmPtr, nMatch int) int { + return cre2.SetMatch(unsafe.Pointer(set.ptr), cs.ptr, cs.length, unsafe.Pointer(matchedPtr), nMatch) +} + +func deleteSet(_ *libre2ABI, setPtr wasmPtr) { + cre2.SetDelete(unsafe.Pointer(setPtr)) +} diff --git a/internal/re2_wazero.go b/internal/re2_wazero.go index 5487669..6d927f6 100644 --- a/internal/re2_wazero.go +++ b/internal/re2_wazero.go @@ -60,9 +60,14 @@ type libre2ABI struct { cre2OptSetCaseSensitive lazyFunction cre2OptSetLatin1Encoding lazyFunction cre2OptSetMaxMem lazyFunction - - malloc lazyFunction - free lazyFunction + cre2SetNew lazyFunction + cre2SetAdd lazyFunction + cre2SetAddSimple lazyFunction + cre2SetCompile lazyFunction + cre2SetMatch lazyFunction + cre2SetDelete lazyFunction + malloc lazyFunction + free lazyFunction } type wasmPtr uint32 @@ -224,9 +229,14 @@ func newABI() *libre2ABI { cre2OptSetCaseSensitive: newLazyFunction("cre2_opt_set_case_sensitive"), cre2OptSetLatin1Encoding: newLazyFunction("cre2_opt_set_latin1_encoding"), cre2OptSetMaxMem: newLazyFunction("cre2_opt_set_max_mem"), - - malloc: newLazyFunction("malloc"), - free: newLazyFunction("free"), + cre2SetNew: newLazyFunction("cre2_set_new"), + cre2SetAdd: newLazyFunction("cre2_set_add"), + cre2SetAddSimple: newLazyFunction("cre2_set_add_simple"), + cre2SetCompile: newLazyFunction("cre2_set_compile"), + cre2SetMatch: newLazyFunction("cre2_set_match"), + cre2SetDelete: newLazyFunction("cre2_set_delete"), + malloc: newLazyFunction("malloc"), + free: newLazyFunction("free"), } return abi @@ -447,6 +457,101 @@ func namedGroupsIterDelete(abi *libre2ABI, iterPtr wasmPtr) { } } +func newSet(abi *libre2ABI, opts CompileOptions) wasmPtr { + ctx := context.Background() + optPtr := uint32(0) + res, err := abi.cre2OptNew.Call0(ctx) + if err != nil { + panic(err) + } + optPtr = uint32(res) + defer func() { + if _, err := abi.cre2OptDelete.Call1(ctx, uint64(optPtr)); err != nil { + panic(err) + } + }() + + _, err = abi.cre2OptSetMaxMem.Call2(ctx, uint64(optPtr), uint64(maxSize)) + if err != nil { + panic(err) + } + + if opts.Longest { + _, err = abi.cre2OptSetLongestMatch.Call2(ctx, uint64(optPtr), 1) + if err != nil { + panic(err) + } + } + if opts.Posix { + _, err = abi.cre2OptSetPosixSyntax.Call2(ctx, uint64(optPtr), 1) + if err != nil { + panic(err) + } + } + if opts.CaseInsensitive { + _, err = abi.cre2OptSetCaseSensitive.Call2(ctx, uint64(optPtr), 0) + if err != nil { + panic(err) + } + } + if opts.Latin1 { + _, err = abi.cre2OptSetLatin1Encoding.Call1(ctx, uint64(optPtr)) + if err != nil { + panic(err) + } + } + + res, err = abi.cre2SetNew.Call2(ctx, uint64(optPtr), 0) + if err != nil { + panic(err) + } + return wasmPtr(res) +} + +func setAddSimple(set *Set, s cString) int32 { + ctx := context.Background() + res, err := set.abi.cre2SetAddSimple.Call2(ctx, uint64(set.ptr), uint64(s.ptr)) + if err != nil { + panic(err) + } + return int32(res) +} + +func setAdd(set *Set, s cString, errors wasmPtr, errorsLen int) int32 { + ctx := context.Background() + res, err := set.abi.cre2SetAdd.Call5(ctx, uint64(set.ptr), uint64(s.ptr), uint64(s.length), uint64(errors), uint64(errorsLen)) + if err != nil { + panic(err) + } + return int32(res) +} + +func setCompile(set *Set) int32 { + ctx := context.Background() + res, err := set.abi.cre2SetCompile.Call1(ctx, uint64(set.ptr)) + if err != nil { + panic(err) + } + return int32(res) +} + +func setMatch(set *Set, cs cString, matchedPtr wasmPtr, nMatch int) int { + ctx := context.Background() + res, err := set.abi.cre2SetMatch.Call5(ctx, uint64(set.ptr), uint64(cs.ptr), uint64(cs.length), uint64(matchedPtr), uint64(nMatch)) + if err != nil { + panic(err) + } + return int(res) +} + +func deleteSet(abi *libre2ABI, setPtr wasmPtr) { + ctx := context.Background() + _, err := abi.cre2SetDelete.Call1(ctx, uint64(setPtr)) + if err != nil { + panic(err) + } +} + type cString struct { ptr wasmPtr length int @@ -579,6 +684,15 @@ func (f *lazyFunction) Call3(ctx context.Context, arg1 uint64, arg2 uint64, arg3 callStack[2] = arg3 return f.callWithStack(ctx, callStack[:]) } +func (f *lazyFunction) Call5(ctx context.Context, arg1 uint64, arg2 uint64, arg3 uint64, arg4 uint64, arg5 uint64) (uint64, error) { + var callStack [5]uint64 + callStack[0] = arg1 + callStack[1] = arg2 + callStack[2] = arg3 + callStack[3] = arg4 + callStack[4] = arg5 + return f.callWithStack(ctx, callStack[:]) +} func (f *lazyFunction) Call8(ctx context.Context, arg1 uint64, arg2 uint64, arg3 uint64, arg4 uint64, arg5 uint64, arg6 uint64, arg7 uint64, arg8 uint64) (uint64, error) { var callStack [8]uint64 diff --git a/internal/wasm/libcre2.wasm b/internal/wasm/libcre2.wasm index 9eace013295a5374be1b82760341979f52478ee6..a8f5454042091895c02e506e31c1543871e35bca 100644 GIT binary patch delta 92600 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 delta 75203 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 H_SpXi`5KUN diff --git a/re2.go b/re2.go index 84372ca..0750a12 100644 --- a/re2.go +++ b/re2.go @@ -8,6 +8,8 @@ import ( type Regexp = internal.Regexp +type Set = internal.Set + // MatchString reports whether the string s // contains any match of the regular expression pattern. // More complicated queries need to use Compile and the full Regexp interface. @@ -46,6 +48,12 @@ func Compile(expr string) (*Regexp, error) { return internal.Compile(expr, internal.CompileOptions{}) } +// CompileSet parses a regular expression and returns, if successful, +// a Set object that can be used to match against text. +func CompileSet(exprs []string) (*Set, error) { + return internal.CompileSet(exprs, internal.CompileOptions{}) +} + // CompilePOSIX is like Compile but restricts the regular expression // to POSIX ERE (egrep) syntax and changes the match semantics to // leftmost-longest. From 5299ed9baa47f1128b9e6b444a78ee88e37825b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Thu, 12 Dec 2024 14:40:04 +0800 Subject: [PATCH 02/14] Move set to experimental package --- all_test.go | 102 ++------------------ example_test.go | 17 ---- experimental/experimental.go | 8 ++ experimental/experimental_test.go | 148 ++++++++++++++++++++++++++++++ internal/re2.go | 41 +++++++++ re2.go | 8 -- 6 files changed, 207 insertions(+), 117 deletions(-) diff --git a/all_test.go b/all_test.go index 46fb73e..8836543 100644 --- a/all_test.go +++ b/all_test.go @@ -47,20 +47,6 @@ var badRe = []stringError{ {strings.Repeat(`\pL`, 27000), "expression too large"}, } -var badSet = []stringError{ - {`*`, "no argument for repetition operator: *"}, - {`+`, "no argument for repetition operator: +"}, - {`?`, "no argument for repetition operator: ?"}, - {`(abc`, "missing ): (abc"}, - {`abc)`, "unexpected ): abc)"}, - {`x[a-z`, "missing ]: [a-z"}, - {`[z-a]`, "invalid character class range: z-a"}, - {`abc\`, "trailing \\"}, - {`a**`, "bad repetition operator: **"}, - {`a*+`, "bad repetition operator: *+"}, - {`\x`, "invalid escape sequence: \\x"}, -} - func compileTest(t *testing.T, expr string, error string) *Regexp { re, err := Compile(expr) if error == "" && err != nil { @@ -86,29 +72,6 @@ func TestBadCompile(t *testing.T) { } } -func TestGoodSetCompile(t *testing.T) { - compileSetTest(t, goodRe, "") -} - -func compileSetTest(t *testing.T, exprs []string, error string) *Set { - set, err := CompileSet(exprs) - if error == "" && err != nil { - t.Error("compiling `", exprs, "`; unexpected error: ", err.Error()) - } - if error != "" && err == nil { - t.Error("compiling `", exprs, "`; missing error") - } else if error != "" && !strings.Contains(err.Error(), error) { - t.Error("compiling `", exprs, "`; wrong error: ", err.Error(), "; want ", error) - } - return set -} - -func TestBadCompileSet(t *testing.T) { - for i := 0; i < len(badSet); i++ { - compileSetTest(t, []string{badSet[i].re}, badSet[i].err) - } -} - func matchTest(t *testing.T, test *FindTest) { re := compileTest(t, test.pat, "") if re == nil { @@ -131,51 +94,6 @@ func TestMatch(t *testing.T) { } } -type SetTest struct { - expr []string - matches map[string][]int -} - -var setTests = []SetTest{ - { - expr: []string{"abc", "\\d+"}, - matches: map[string][]int{ - "abc": {0}, - "123": {1}, - "abc123": {0, 1}, - "def": {}, - }, - }, - { - expr: []string{"[a-c]+", "(d)(e){0}(f)"}, - matches: map[string][]int{ - "a234v": {0}, - "df": {1}, - "abcdf": {0, 1}, - "def": {}, - }, - }, -} - -func setMatchTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { - m := set.Match([]byte(matchStr), 10) - if !reflect.DeepEqual(m, matchedIds) { - t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) - } -} - -func TestSetMatch(t *testing.T) { - for _, test := range setTests { - set := compileSetTest(t, test.expr, "") - if set == nil { - return - } - for matchStr, matchedIds := range test.matches { - setMatchTest(t, set, matchStr, matchedIds) - } - } -} - func matchFunctionTest(t *testing.T, test *FindTest) { m, err := MatchString(test.pat, test.text) if err == nil { @@ -484,16 +402,16 @@ type subexpCase struct { var emptySubexpIndices = []subexpIndex{{"", -1}, {"missing", -1}} var subexpCases = []subexpCase{ - {``, 0, nil, emptySubexpIndices}, - {`.*`, 0, nil, emptySubexpIndices}, - {`abba`, 0, nil, emptySubexpIndices}, - {`ab(b)a`, 1, []string{"", ""}, emptySubexpIndices}, - {`ab(.*)a`, 1, []string{"", ""}, emptySubexpIndices}, - {`(.*)ab(.*)a`, 2, []string{"", "", ""}, emptySubexpIndices}, - {`(.*)(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, - {`(.*)((a)b)(.*)a`, 4, []string{"", "", "", "", ""}, emptySubexpIndices}, - {`(.*)(\(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, - {`(.*)(\(a\)b)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, + //{``, 0, nil, emptySubexpIndices}, + //{`.*`, 0, nil, emptySubexpIndices}, + //{`abba`, 0, nil, emptySubexpIndices}, + //{`ab(b)a`, 1, []string{"", ""}, emptySubexpIndices}, + //{`ab(.*)a`, 1, []string{"", ""}, emptySubexpIndices}, + //{`(.*)ab(.*)a`, 2, []string{"", "", ""}, emptySubexpIndices}, + //{`(.*)(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, + //{`(.*)((a)b)(.*)a`, 4, []string{"", "", "", "", ""}, emptySubexpIndices}, + //{`(.*)(\(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, + //{`(.*)(\(a\)b)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, {`(?P.*)(?P(a)b)(?P.*)a`, 4, []string{"", "foo", "bar", "", "foo"}, []subexpIndex{{"", -1}, {"missing", -1}, {"foo", 1}, {"bar", 2}}}, } diff --git a/example_test.go b/example_test.go index b3a3a97..235db6f 100644 --- a/example_test.go +++ b/example_test.go @@ -426,20 +426,3 @@ func ExampleRegexp_FindAllIndex() { // [[1 3]] // [[1 3] [4 6]] } - -func ExampleCompileSet() { - exprs := []string{"abc", "\\d+"} - set, err := regexp.CompileSet(exprs) - if err != nil { - panic(err) - } - fmt.Println(set.Match([]byte("abcd"), len(exprs))) - fmt.Println(set.Match([]byte("123"), len(exprs))) - fmt.Println(set.Match([]byte("abc123"), len(exprs))) - fmt.Println(set.Match([]byte("def"), len(exprs))) - // Output: - // [0] - // [1] - // [0 1] - // [] -} diff --git a/experimental/experimental.go b/experimental/experimental.go index 276e377..4c7f700 100644 --- a/experimental/experimental.go +++ b/experimental/experimental.go @@ -22,3 +22,11 @@ func MustCompileLatin1(str string) *re2.Regexp { } return regexp } + +type Set = internal.Set + +// CompileSet parses a regular expression and returns, if successful, +// a Set object that can be used to match against text. +func CompileSet(exprs []string) (*Set, error) { + return internal.CompileSet(exprs, internal.CompileOptions{}) +} diff --git a/experimental/experimental_test.go b/experimental/experimental_test.go index 71e397f..3081261 100644 --- a/experimental/experimental_test.go +++ b/experimental/experimental_test.go @@ -2,6 +2,9 @@ package experimental import ( "fmt" + "github.com/wasilibs/go-re2" + "reflect" + "strings" "testing" ) @@ -55,3 +58,148 @@ func TestCompileLatin1(t *testing.T) { }) } } + +var goodRe = []string{ + ``, + `.`, + `^.$`, + `a`, + `a*`, + `a+`, + `a?`, + `a|b`, + `a*|b*`, + `(a*|b)(c*|d)`, + `[a-z]`, + `[a-abc-c\-\]\[]`, + `[a-z]+`, + `[abc]`, + `[^1234]`, + `[^\n]`, + `\!\\`, +} + +type stringError struct { + re string + err string +} + +var badSet = []stringError{ + {`*`, "no argument for repetition operator: *"}, + {`+`, "no argument for repetition operator: +"}, + {`?`, "no argument for repetition operator: ?"}, + {`(abc`, "missing ): (abc"}, + {`abc)`, "unexpected ): abc)"}, + {`x[a-z`, "missing ]: [a-z"}, + {`[z-a]`, "invalid character class range: z-a"}, + {`abc\`, "trailing \\"}, + {`a**`, "bad repetition operator: **"}, + {`a*+`, "bad repetition operator: *+"}, + {`\x`, "invalid escape sequence: \\x"}, +} + +func TestGoodSetCompile(t *testing.T) { + compileSetTest(t, goodRe, "") +} + +func compileSetTest(t *testing.T, exprs []string, error string) *Set { + set, err := CompileSet(exprs) + if error == "" && err != nil { + t.Error("compiling `", exprs, "`; unexpected error: ", err.Error()) + } + if error != "" && err == nil { + t.Error("compiling `", exprs, "`; missing error") + } else if error != "" && !strings.Contains(err.Error(), error) { + t.Error("compiling `", exprs, "`; wrong error: ", err.Error(), "; want ", error) + } + return set +} + +func TestBadCompileSet(t *testing.T) { + for i := 0; i < len(badSet); i++ { + compileSetTest(t, []string{badSet[i].re}, badSet[i].err) + } +} + +type SetTest struct { + expr []string + matches map[string][]int +} + +var setTests = []SetTest{ + { + expr: []string{"abc", "\\d+"}, + matches: map[string][]int{ + "abc": {0}, + "123": {1}, + "abc123": {0, 1}, + "def": {}, + }, + }, + { + expr: []string{"[a-c]+", "(d)(e){0}(f)"}, + matches: map[string][]int{ + "a234v": {0}, + "df": {1}, + "abcdf": {0, 1}, + "def": {}, + }, + }, +} + +func setMatchTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { + m := set.Match([]byte(matchStr), 10) + if !reflect.DeepEqual(m, matchedIds) { + t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) + } +} + +func TestSetMatch(t *testing.T) { + for _, test := range setTests { + set := compileSetTest(t, test.expr, "") + if set == nil { + return + } + for matchStr, matchedIds := range test.matches { + setMatchTest(t, set, matchStr, matchedIds) + } + } +} + +func BenchmarkSetMatchWithFindSubmatch(b *testing.B) { + b.Run("set match", func(b *testing.B) { + set, err := CompileSet(goodRe) + if err != nil { + panic(err) + } + for i := 0; i < b.N; i++ { + set.Match([]byte("abcd123"), 20) + } + }) + b.Run("findSubmatch", func(b *testing.B) { + re, err := re2.Compile("(" + strings.Join(goodRe, ")|(") + ")") + if err != nil { + panic(err) + } + for i := 0; i < b.N; i++ { + re.FindAllStringSubmatchIndex("abcd123", 20) + } + }) +} + +func ExampleCompileSet() { + exprs := []string{"abc", "\\d+"} + set, err := CompileSet(exprs) + if err != nil { + panic(err) + } + fmt.Println(set.Match([]byte("abcd"), len(exprs))) + fmt.Println(set.Match([]byte("123"), len(exprs))) + fmt.Println(set.Match([]byte("abc123"), len(exprs))) + fmt.Println(set.Match([]byte("def"), len(exprs))) + // Output: + // [0] + // [1] + // [0 1] + // [] +} diff --git a/internal/re2.go b/internal/re2.go index 4515b1f..c6716a1 100644 --- a/internal/re2.go +++ b/internal/re2.go @@ -100,6 +100,21 @@ func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { return set, nil } +func NewSet(opts CompileOptions) (*Set, error) { + abi := newABI() + setPtr := newSet(abi, opts) + set := &Set{ + ptr: setPtr, + abi: abi, + opts: opts, + } + // Use func(interface{}) form for nottinygc compatibility. + runtime.SetFinalizer(set, func(obj interface{}) { + obj.(*Set).release() + }) + return set, nil +} + func readErrorMessage(alloc *allocation, ptr wasmPtr, length int) string { errMsgBytes := alloc.read(ptr, length) nulIndex := bytes.IndexByte(errMsgBytes, 0) @@ -116,6 +131,32 @@ func (set *Set) release() { deleteSet(set.abi, set.ptr) } +func (set *Set) Compile(expr string) { + setCompile(set) +} + +func (set *Set) SetAdd(expr string) error { + alloc := set.abi.startOperation(len(expr) + 2 + 8) + defer set.abi.endOperation(alloc) + + cs := alloc.newCString(expr) + errorBuffer := alloc.newCStringArray(1) + res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength) + if res == -1 { + errorMessage := readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength) + return errors.New(errorMessage) + } + return nil +} + +func (set *Set) SetAddSimple(expr string) { + alloc := set.abi.startOperation(len(expr) + 2 + 8) + defer set.abi.endOperation(alloc) + + cs := alloc.newCString(expr) + setAddSimple(set, cs) +} + func (set *Set) Match(b []byte, n int) []int { alloc := set.abi.startOperation(len(b) + 8 + n*8) defer set.abi.endOperation(alloc) diff --git a/re2.go b/re2.go index 0750a12..84372ca 100644 --- a/re2.go +++ b/re2.go @@ -8,8 +8,6 @@ import ( type Regexp = internal.Regexp -type Set = internal.Set - // MatchString reports whether the string s // contains any match of the regular expression pattern. // More complicated queries need to use Compile and the full Regexp interface. @@ -48,12 +46,6 @@ func Compile(expr string) (*Regexp, error) { return internal.Compile(expr, internal.CompileOptions{}) } -// CompileSet parses a regular expression and returns, if successful, -// a Set object that can be used to match against text. -func CompileSet(exprs []string) (*Set, error) { - return internal.CompileSet(exprs, internal.CompileOptions{}) -} - // CompilePOSIX is like Compile but restricts the regular expression // to POSIX ERE (egrep) syntax and changes the match semantics to // leftmost-longest. From 1a02f0bfcf552cfa9943acf1b81030ad86220c67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Thu, 12 Dec 2024 15:35:26 +0800 Subject: [PATCH 03/14] Fix SetAdd --- all_test.go | 20 ++++++++++---------- internal/re2.go | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/all_test.go b/all_test.go index 8836543..3d71ea5 100644 --- a/all_test.go +++ b/all_test.go @@ -402,16 +402,16 @@ type subexpCase struct { var emptySubexpIndices = []subexpIndex{{"", -1}, {"missing", -1}} var subexpCases = []subexpCase{ - //{``, 0, nil, emptySubexpIndices}, - //{`.*`, 0, nil, emptySubexpIndices}, - //{`abba`, 0, nil, emptySubexpIndices}, - //{`ab(b)a`, 1, []string{"", ""}, emptySubexpIndices}, - //{`ab(.*)a`, 1, []string{"", ""}, emptySubexpIndices}, - //{`(.*)ab(.*)a`, 2, []string{"", "", ""}, emptySubexpIndices}, - //{`(.*)(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, - //{`(.*)((a)b)(.*)a`, 4, []string{"", "", "", "", ""}, emptySubexpIndices}, - //{`(.*)(\(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, - //{`(.*)(\(a\)b)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, + {``, 0, nil, emptySubexpIndices}, + {`.*`, 0, nil, emptySubexpIndices}, + {`abba`, 0, nil, emptySubexpIndices}, + {`ab(b)a`, 1, []string{"", ""}, emptySubexpIndices}, + {`ab(.*)a`, 1, []string{"", ""}, emptySubexpIndices}, + {`(.*)ab(.*)a`, 2, []string{"", "", ""}, emptySubexpIndices}, + {`(.*)(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, + {`(.*)((a)b)(.*)a`, 4, []string{"", "", "", "", ""}, emptySubexpIndices}, + {`(.*)(\(ab)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, + {`(.*)(\(a\)b)(.*)a`, 3, []string{"", "", "", ""}, emptySubexpIndices}, {`(?P.*)(?P(a)b)(?P.*)a`, 4, []string{"", "foo", "bar", "", "foo"}, []subexpIndex{{"", -1}, {"missing", -1}, {"foo", 1}, {"bar", 2}}}, } diff --git a/internal/re2.go b/internal/re2.go index c6716a1..1bb01b4 100644 --- a/internal/re2.go +++ b/internal/re2.go @@ -141,10 +141,10 @@ func (set *Set) SetAdd(expr string) error { cs := alloc.newCString(expr) errorBuffer := alloc.newCStringArray(1) - res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength) - if res == -1 { - errorMessage := readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength) - return errors.New(errorMessage) + defer errorBuffer.free() + + if res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength); res == -1 { + return errors.New(readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength)) } return nil } From 42b7c36788e677b877705127cb692bdcc0b8dfe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Fri, 13 Dec 2024 09:41:41 +0800 Subject: [PATCH 04/14] Fix CompileSet --- experimental/experimental_test.go | 4 ++- internal/re2.go | 57 ++++++++++++++++--------------- internal/re2_re2_cgo.go | 7 +++- internal/re2_wazero.go | 13 +++++++ 4 files changed, 51 insertions(+), 30 deletions(-) diff --git a/experimental/experimental_test.go b/experimental/experimental_test.go index 3081261..62cca67 100644 --- a/experimental/experimental_test.go +++ b/experimental/experimental_test.go @@ -2,10 +2,11 @@ package experimental import ( "fmt" - "github.com/wasilibs/go-re2" "reflect" "strings" "testing" + + "github.com/wasilibs/go-re2" ) func TestCompileLatin1(t *testing.T) { @@ -96,6 +97,7 @@ var badSet = []stringError{ {`a**`, "bad repetition operator: **"}, {`a*+`, "bad repetition operator: *+"}, {`\x`, "invalid escape sequence: \\x"}, + {strings.Repeat(`)\pL`, 27000), "unexpected ): " + strings.Repeat(`)\pL`, 27000)}, } func TestGoodSetCompile(t *testing.T) { diff --git a/internal/re2.go b/internal/re2.go index 1bb01b4..053aef6 100644 --- a/internal/re2.go +++ b/internal/re2.go @@ -76,20 +76,25 @@ func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { abi: abi, opts: opts, } - estimatedMemorySize := errorBufferLength + var errorBufferLen int + var estimatedMemorySize int for _, expr := range exprs { + errorBufferLen = max(errorBufferLen, len(expr)) estimatedMemorySize += len(expr) + 2 + 8 } - alloc := abi.startOperation(estimatedMemorySize) + + alloc := abi.startOperation(estimatedMemorySize + errorBufferLen + errorBufferLength) defer abi.endOperation(alloc) - errorBuffer := alloc.newCStringArray(1) + errorBuffer := alloc.newCStringArray((errorBufferLen + errorBufferLength) / 16) + defer errorBuffer.free() + for _, expr := range exprs { + errorsLen := len(expr) + errorBufferLength cs := alloc.newCString(expr) - res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength) + res := setAdd(set, cs, errorBuffer.ptr, errorsLen) if res == -1 { - errorMessage := readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength) - return nil, errors.New(errorMessage) + return nil, errors.New(readErr(errorBuffer.ptr, errorsLen)) } } setCompile(set) @@ -115,15 +120,6 @@ func NewSet(opts CompileOptions) (*Set, error) { return set, nil } -func readErrorMessage(alloc *allocation, ptr wasmPtr, length int) string { - errMsgBytes := alloc.read(ptr, length) - nulIndex := bytes.IndexByte(errMsgBytes, 0) - if nulIndex != -1 { - errMsgBytes = errMsgBytes[:nulIndex] - } - return string(errMsgBytes) -} - func (set *Set) release() { if !atomic.CompareAndSwapUint32(&set.released, 0, 1) { return @@ -133,21 +129,24 @@ func (set *Set) release() { func (set *Set) Compile(expr string) { setCompile(set) + runtime.KeepAlive(set) } -func (set *Set) SetAdd(expr string) error { - alloc := set.abi.startOperation(len(expr) + 2 + 8) - defer set.abi.endOperation(alloc) - - cs := alloc.newCString(expr) - errorBuffer := alloc.newCStringArray(1) - defer errorBuffer.free() - - if res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength); res == -1 { - return errors.New(readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength)) - } - return nil -} +//func (set *Set) SetAdd(expr string) error { +// alloc := set.abi.startOperation(len(expr) + 2 + 8) +// defer set.abi.endOperation(alloc) +// +// cs := alloc.newCString(expr) +// errorBuffer := alloc.newCStringArray(1) +// defer errorBuffer.free() +// +// if res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength); res == -1 { +// return errors.New(readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength)) +// } +// +// runtime.KeepAlive(set) +// return nil +//} func (set *Set) SetAddSimple(expr string) { alloc := set.abi.startOperation(len(expr) + 2 + 8) @@ -155,6 +154,8 @@ func (set *Set) SetAddSimple(expr string) { cs := alloc.newCString(expr) setAddSimple(set, cs) + + runtime.KeepAlive(set) } func (set *Set) Match(b []byte, n int) []int { diff --git a/internal/re2_re2_cgo.go b/internal/re2_re2_cgo.go index b98edc5..f35075e 100644 --- a/internal/re2_re2_cgo.go +++ b/internal/re2_re2_cgo.go @@ -3,8 +3,9 @@ package internal import ( - "github.com/wasilibs/go-re2/internal/cre2" "unsafe" + + "github.com/wasilibs/go-re2/internal/cre2" ) type wasmPtr unsafe.Pointer @@ -117,6 +118,10 @@ func (a *allocation) read(ptr wasmPtr, size int) []byte { return (*[1 << 30]byte)(unsafe.Pointer(ptr))[:size:size] } +func readErr(ptr wasmPtr, _ int) string { + return cre2.CopyCString(unsafe.Pointer(ptr)) +} + type cString struct { ptr unsafe.Pointer length int diff --git a/internal/re2_wazero.go b/internal/re2_wazero.go index 6d927f6..c9a3cb1 100644 --- a/internal/re2_wazero.go +++ b/internal/re2_wazero.go @@ -618,6 +618,18 @@ func (a *allocation) read(ptr wasmPtr, size int) []byte { return buf } +func readErr(ptr wasmPtr, errorsLen int) string { + buf, ok := wasmMemory.Read(uint32(ptr), uint32(errorsLen)) // Assuming a maximum length of 1024 for the string + if !ok { + panic("failed to read from wasm memory") + } + end := 0 + for end < len(buf) && buf[end] != 0 { + end++ + } + return string(buf[:end]) +} + func (a *allocation) write(b []byte) wasmPtr { ptr := a.allocate(uint32(len(b))) wasmMemory.Write(uint32(ptr), b) @@ -684,6 +696,7 @@ func (f *lazyFunction) Call3(ctx context.Context, arg1 uint64, arg2 uint64, arg3 callStack[2] = arg3 return f.callWithStack(ctx, callStack[:]) } + func (f *lazyFunction) Call5(ctx context.Context, arg1 uint64, arg2 uint64, arg3 uint64, arg4 uint64, arg5 uint64) (uint64, error) { var callStack [5]uint64 callStack[0] = arg1 From bb7215b984976e50c0073dc441d6e7319e67e6a0 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 11:07:17 +0900 Subject: [PATCH 05/14] Separate set.go --- internal/re2.go | 122 ----------------------------------------- internal/re2_wazero.go | 18 +++--- 2 files changed, 10 insertions(+), 130 deletions(-) diff --git a/internal/re2.go b/internal/re2.go index 053aef6..4631556 100644 --- a/internal/re2.go +++ b/internal/re2.go @@ -2,8 +2,6 @@ package internal import ( "bytes" - "encoding/binary" - "errors" "fmt" "runtime" "strconv" @@ -18,13 +16,6 @@ import ( // https://github.com/golang/go/blob/master/src/regexp/syntax/parse.go#L95 const maxSize = 128 << 20 -type Set struct { - ptr wasmPtr - abi *libre2ABI - opts CompileOptions - released uint32 -} - type Regexp struct { ptr wasmPtr @@ -66,119 +57,6 @@ type CompileOptions struct { Latin1 bool } -const errorBufferLength = 64 - -func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { - abi := newABI() - setPtr := newSet(abi, opts) - set := &Set{ - ptr: setPtr, - abi: abi, - opts: opts, - } - var errorBufferLen int - var estimatedMemorySize int - for _, expr := range exprs { - errorBufferLen = max(errorBufferLen, len(expr)) - estimatedMemorySize += len(expr) + 2 + 8 - } - - alloc := abi.startOperation(estimatedMemorySize + errorBufferLen + errorBufferLength) - defer abi.endOperation(alloc) - - errorBuffer := alloc.newCStringArray((errorBufferLen + errorBufferLength) / 16) - defer errorBuffer.free() - - for _, expr := range exprs { - errorsLen := len(expr) + errorBufferLength - cs := alloc.newCString(expr) - res := setAdd(set, cs, errorBuffer.ptr, errorsLen) - if res == -1 { - return nil, errors.New(readErr(errorBuffer.ptr, errorsLen)) - } - } - setCompile(set) - // Use func(interface{}) form for nottinygc compatibility. - runtime.SetFinalizer(set, func(obj interface{}) { - obj.(*Set).release() - }) - return set, nil -} - -func NewSet(opts CompileOptions) (*Set, error) { - abi := newABI() - setPtr := newSet(abi, opts) - set := &Set{ - ptr: setPtr, - abi: abi, - opts: opts, - } - // Use func(interface{}) form for nottinygc compatibility. - runtime.SetFinalizer(set, func(obj interface{}) { - obj.(*Set).release() - }) - return set, nil -} - -func (set *Set) release() { - if !atomic.CompareAndSwapUint32(&set.released, 0, 1) { - return - } - deleteSet(set.abi, set.ptr) -} - -func (set *Set) Compile(expr string) { - setCompile(set) - runtime.KeepAlive(set) -} - -//func (set *Set) SetAdd(expr string) error { -// alloc := set.abi.startOperation(len(expr) + 2 + 8) -// defer set.abi.endOperation(alloc) -// -// cs := alloc.newCString(expr) -// errorBuffer := alloc.newCStringArray(1) -// defer errorBuffer.free() -// -// if res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength); res == -1 { -// return errors.New(readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength)) -// } -// -// runtime.KeepAlive(set) -// return nil -//} - -func (set *Set) SetAddSimple(expr string) { - alloc := set.abi.startOperation(len(expr) + 2 + 8) - defer set.abi.endOperation(alloc) - - cs := alloc.newCString(expr) - setAddSimple(set, cs) - - runtime.KeepAlive(set) -} - -func (set *Set) Match(b []byte, n int) []int { - alloc := set.abi.startOperation(len(b) + 8 + n*8) - defer set.abi.endOperation(alloc) - - matchArr := alloc.newCStringArray(n) - defer matchArr.free() - - cs := alloc.newCStringFromBytes(b) - matchedCount := setMatch(set, cs, matchArr.ptr, n) - matchedIDs := make([]int, min(matchedCount, n)) - matches := alloc.read(matchArr.ptr, n*4) - for i := 0; i < len(matchedIDs); i++ { - matchedIDs[i] = int(binary.LittleEndian.Uint32(matches[i*4:])) - } - - runtime.KeepAlive(b) - runtime.KeepAlive(matchArr) - runtime.KeepAlive(set) // don't allow finalizer to run during method - return matchedIDs -} - func Compile(expr string, opts CompileOptions) (*Regexp, error) { abi := newABI() alloc := abi.startOperation(len(expr) + 2 + 8) diff --git a/internal/re2_wazero.go b/internal/re2_wazero.go index c9a3cb1..4f0db3a 100644 --- a/internal/re2_wazero.go +++ b/internal/re2_wazero.go @@ -60,14 +60,16 @@ type libre2ABI struct { cre2OptSetCaseSensitive lazyFunction cre2OptSetLatin1Encoding lazyFunction cre2OptSetMaxMem lazyFunction - cre2SetNew lazyFunction - cre2SetAdd lazyFunction - cre2SetAddSimple lazyFunction - cre2SetCompile lazyFunction - cre2SetMatch lazyFunction - cre2SetDelete lazyFunction - malloc lazyFunction - free lazyFunction + + cre2SetNew lazyFunction + cre2SetAdd lazyFunction + cre2SetAddSimple lazyFunction + cre2SetCompile lazyFunction + cre2SetMatch lazyFunction + cre2SetDelete lazyFunction + + malloc lazyFunction + free lazyFunction } type wasmPtr uint32 From a3b30750b22a3eb7d87976a7b574c7ca3791f500 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 11:22:37 +0900 Subject: [PATCH 06/14] git add --- internal/set.go | 128 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 internal/set.go diff --git a/internal/set.go b/internal/set.go new file mode 100644 index 0000000..4e93d61 --- /dev/null +++ b/internal/set.go @@ -0,0 +1,128 @@ +package internal + +import ( + "encoding/binary" + "errors" + "runtime" + "sync/atomic" +) + +const errorBufferLength = 64 + +type Set struct { + ptr wasmPtr + abi *libre2ABI + opts CompileOptions + released uint32 +} + +func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { + abi := newABI() + setPtr := newSet(abi, opts) + set := &Set{ + ptr: setPtr, + abi: abi, + opts: opts, + } + var errorBufferLen int + var estimatedMemorySize int + for _, expr := range exprs { + errorBufferLen = max(errorBufferLen, len(expr)) + estimatedMemorySize += len(expr) + 2 + 8 + } + + alloc := abi.startOperation(estimatedMemorySize + errorBufferLen + errorBufferLength) + defer abi.endOperation(alloc) + + errorBuffer := alloc.newCStringArray((errorBufferLen + errorBufferLength) / 16) + defer errorBuffer.free() + + for _, expr := range exprs { + errorsLen := len(expr) + errorBufferLength + cs := alloc.newCString(expr) + res := setAdd(set, cs, errorBuffer.ptr, errorsLen) + if res == -1 { + return nil, errors.New(readErr(errorBuffer.ptr, errorsLen)) + } + } + setCompile(set) + // Use func(interface{}) form for nottinygc compatibility. + runtime.SetFinalizer(set, func(obj interface{}) { + obj.(*Set).release() + }) + return set, nil +} + +func NewSet(opts CompileOptions) (*Set, error) { + abi := newABI() + setPtr := newSet(abi, opts) + set := &Set{ + ptr: setPtr, + abi: abi, + opts: opts, + } + // Use func(interface{}) form for nottinygc compatibility. + runtime.SetFinalizer(set, func(obj interface{}) { + obj.(*Set).release() + }) + return set, nil +} + +func (set *Set) release() { + if !atomic.CompareAndSwapUint32(&set.released, 0, 1) { + return + } + deleteSet(set.abi, set.ptr) +} + +func (set *Set) Compile(expr string) { + setCompile(set) + runtime.KeepAlive(set) +} + +//func (set *Set) SetAdd(expr string) error { +// alloc := set.abi.startOperation(len(expr) + 2 + 8) +// defer set.abi.endOperation(alloc) +// +// cs := alloc.newCString(expr) +// errorBuffer := alloc.newCStringArray(1) +// defer errorBuffer.free() +// +// if res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength); res == -1 { +// return errors.New(readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength)) +// } +// +// runtime.KeepAlive(set) +// return nil +//} + +func (set *Set) SetAddSimple(expr string) { + alloc := set.abi.startOperation(len(expr) + 2 + 8) + defer set.abi.endOperation(alloc) + + cs := alloc.newCString(expr) + setAddSimple(set, cs) + + runtime.KeepAlive(set) +} + +func (set *Set) Match(b []byte, n int) []int { + alloc := set.abi.startOperation(len(b) + 8 + n*8) + defer set.abi.endOperation(alloc) + + matchArr := alloc.newCStringArray(n) + defer matchArr.free() + + cs := alloc.newCStringFromBytes(b) + matchedCount := setMatch(set, cs, matchArr.ptr, n) + matchedIDs := make([]int, min(matchedCount, n)) + matches := alloc.read(matchArr.ptr, n*4) + for i := 0; i < len(matchedIDs); i++ { + matchedIDs[i] = int(binary.LittleEndian.Uint32(matches[i*4:])) + } + + runtime.KeepAlive(b) + runtime.KeepAlive(matchArr) + runtime.KeepAlive(set) // don't allow finalizer to run during method + return matchedIDs +} From b649a8110085a493c1e07bc635f004b06f0acd86 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 12:21:08 +0900 Subject: [PATCH 07/14] WIP --- internal/cre2/cre2.cpp | 30 ++++++++++++++++-------------- internal/cre2/cre2.h | 6 ++---- internal/re2_wazero.go | 6 +++--- internal/set.go | 11 ++++++----- internal/wasm/libcre2.wasm | Bin 3270506 -> 3271373 bytes 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/internal/cre2/cre2.cpp b/internal/cre2/cre2.cpp index 4ae5991..1542acf 100644 --- a/internal/cre2/cre2.cpp +++ b/internal/cre2/cre2.cpp @@ -661,24 +661,26 @@ cre2_set_delete(cre2_set *set) delete TO_RE2_SET(set); } -// Add a regex to the set. If invalid: store error message in error buffer. -int -cre2_set_add(cre2_set *set, const char *pattern, size_t pattern_len, char *error, size_t error_len) +static char* ok = "ok"; + +// Add a regex to the set. If invalid: return error message that must be freed. If valid, return "ok". +char* +cre2_set_add(cre2_set *set, const char *pattern, size_t pattern_len) { RE2::Set *s = TO_RE2_SET(set); re2::StringPiece regex(pattern, static_cast(pattern_len)); - if ((NULL == error) || (0 == error_len)) { - return s->Add(regex, NULL); - } else { - std::string err; - int regex_index = s->Add(regex, &err); - if (regex_index < 0) { - size_t len = err.size() < error_len - 1 ? err.size() : error_len - 1; - err.copy(error, len); - error[len] = '\0'; - } - return regex_index; + std::string err; + int regex_index = s->Add(regex, &err); + if (regex_index >= 0) { + return ok; + } + size_t len = err.size() + 1; + void* msg = malloc(len); + if (msg == NULL) { + return NULL; } + memcpy(msg, err.c_str(), len); + return (char*)msg; } // Add pattern without NULL byte. Don't store error message. diff --git a/internal/cre2/cre2.h b/internal/cre2/cre2.h index 7fae912..dbb161f 100644 --- a/internal/cre2/cre2.h +++ b/internal/cre2/cre2.h @@ -313,10 +313,8 @@ cre2_decl cre2_set *cre2_set_new(cre2_options_t *opt, cre2_anchor_t anchor); /* RE2::Set destructor */ cre2_decl void cre2_set_delete(cre2_set *set); -/* Add a regex to the set. If invalid: store error message in error buffer. - * Returns the index associated to this regex, -1 on error */ -cre2_decl int cre2_set_add(cre2_set *set, const char *pattern, size_t pattern_len, - char *error, size_t error_len); +// Add a regex to the set. If invalid: return error message that must be freed. If valid, return "ok". +cre2_decl char* cre2_set_add(cre2_set *set, const char *pattern, size_t pattern_len); /* Add pattern without NULL byte. Discard error message. * Returns the index associated to this regex, -1 on error */ diff --git a/internal/re2_wazero.go b/internal/re2_wazero.go index 4f0db3a..bff8b15 100644 --- a/internal/re2_wazero.go +++ b/internal/re2_wazero.go @@ -519,13 +519,13 @@ func setAddSimple(set *Set, s cString) int32 { return int32(res) } -func setAdd(set *Set, s cString, errors wasmPtr, errorsLen int) int32 { +func setAdd(set *Set, s cString) wasmPtr { ctx := context.Background() - res, err := set.abi.cre2SetAdd.Call5(ctx, uint64(set.ptr), uint64(s.ptr), uint64(s.length), uint64(errors), uint64(errorsLen)) + res, err := set.abi.cre2SetAdd.Call3(ctx, uint64(set.ptr), uint64(s.ptr), uint64(s.length)) if err != nil { panic(err) } - return int32(res) + return wasmPtr(res) } func setCompile(set *Set) int32 { diff --git a/internal/set.go b/internal/set.go index 4e93d61..126d9b2 100644 --- a/internal/set.go +++ b/internal/set.go @@ -7,6 +7,8 @@ import ( "sync/atomic" ) +var errErrorUnknown = errors.New("unknown error compiling pattern") + const errorBufferLength = 64 type Set struct { @@ -34,13 +36,12 @@ func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { alloc := abi.startOperation(estimatedMemorySize + errorBufferLen + errorBufferLength) defer abi.endOperation(alloc) - errorBuffer := alloc.newCStringArray((errorBufferLen + errorBufferLength) / 16) - defer errorBuffer.free() - for _, expr := range exprs { - errorsLen := len(expr) + errorBufferLength cs := alloc.newCString(expr) - res := setAdd(set, cs, errorBuffer.ptr, errorsLen) + res := setAdd(set, cs) + if res == 0 { + return nil, errErrorUnknown + } if res == -1 { return nil, errors.New(readErr(errorBuffer.ptr, errorsLen)) } diff --git a/internal/wasm/libcre2.wasm b/internal/wasm/libcre2.wasm index a8f5454042091895c02e506e31c1543871e35bca..e71bed776ad897865f633bdb2a43f6e039d2c9ce 100644 GIT binary patch delta 19642 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 delta 18862 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 From 2d4dbb9b6d76842bba175b260b57a3d5ca15ea79 Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 13:00:07 +0900 Subject: [PATCH 08/14] Mostly done --- experimental/experimental.go | 4 +- internal/cre2/cre2.go | 52 +++++++++++----------- internal/re2_re2_cgo.go | 18 +++++--- internal/re2_wazero.go | 47 +++++++------------- internal/set.go | 86 +++++++++--------------------------- 5 files changed, 77 insertions(+), 130 deletions(-) diff --git a/experimental/experimental.go b/experimental/experimental.go index 4c7f700..c963553 100644 --- a/experimental/experimental.go +++ b/experimental/experimental.go @@ -23,10 +23,10 @@ func MustCompileLatin1(str string) *re2.Regexp { return regexp } +// Set is a compiled collection of regular expressions that can be searched for simultaneously. type Set = internal.Set -// CompileSet parses a regular expression and returns, if successful, -// a Set object that can be used to match against text. +// CompileSet compiles the set of regular expression in preparation for matching. func CompileSet(exprs []string) (*Set, error) { return internal.CompileSet(exprs, internal.CompileOptions{}) } diff --git a/internal/cre2/cre2.go b/internal/cre2/cre2.go index c82acdf..b5f0190 100644 --- a/internal/cre2/cre2.go +++ b/internal/cre2/cre2.go @@ -27,8 +27,7 @@ void cre2_opt_set_case_sensitive(void* opt, int flag); void cre2_opt_set_latin1_encoding(void* opt); void cre2_opt_set_max_mem(void* opt, int64_t size); void* cre2_set_new(void* opt, int anchor); -int cre2_set_add_simple(void* set, void* pattern); -int cre2_set_add(void* set, void* pattern, int pattern_len, void* errors, int errors_len); +void* cre2_set_add(void* set, void* pattern, int pattern_len); int cre2_set_compile(void* set); int cre2_set_match(void* set, void* text, int text_len, void* match, int nmatch); void cre2_set_delete(void* set); @@ -37,7 +36,10 @@ void* malloc(size_t size); void free(void* ptr); */ import "C" -import "unsafe" + +import ( + "unsafe" +) func New(patternPtr unsafe.Pointer, patternLen int, opts unsafe.Pointer) unsafe.Pointer { return C.cre2_new(patternPtr, C.int(patternLen), opts) @@ -94,30 +96,6 @@ func DeleteOpt(opt unsafe.Pointer) { C.cre2_opt_delete(opt) } -func NewSet(opt unsafe.Pointer, anchor int) unsafe.Pointer { - return C.cre2_set_new(opt, C.int(anchor)) -} - -func SetAddSimple(set unsafe.Pointer, patternPtr unsafe.Pointer) int { - return int(C.cre2_set_add_simple(set, patternPtr)) -} - -func SetAdd(set unsafe.Pointer, patternPtr unsafe.Pointer, patternLen int, errors unsafe.Pointer, errorLen int) int { - return int(C.cre2_set_add(set, patternPtr, C.int(patternLen), errors, C.int(errorLen))) -} - -func SetCompile(set unsafe.Pointer) int { - return int(C.cre2_set_compile(set)) -} - -func SetMatch(set unsafe.Pointer, textPtr unsafe.Pointer, textLen int, match unsafe.Pointer, nMatch int) int { - return int(C.cre2_set_match(set, textPtr, C.int(textLen), match, C.int(nMatch))) -} - -func SetDelete(ptr unsafe.Pointer) { - C.cre2_set_delete(ptr) -} - func OptSetLogErrors(opt unsafe.Pointer, flag bool) { C.cre2_opt_set_log_errors(opt, cFlag(flag)) } @@ -142,6 +120,26 @@ func OptSetMaxMem(opt unsafe.Pointer, size int) { C.cre2_opt_set_max_mem(opt, C.int64_t(size)) } +func NewSet(opt unsafe.Pointer, anchor int) unsafe.Pointer { + return C.cre2_set_new(opt, C.int(anchor)) +} + +func SetAdd(set unsafe.Pointer, patternPtr unsafe.Pointer, patternLen int) unsafe.Pointer { + return C.cre2_set_add(set, patternPtr, C.int(patternLen)) +} + +func SetCompile(set unsafe.Pointer) int { + return int(C.cre2_set_compile(set)) +} + +func SetMatch(set unsafe.Pointer, textPtr unsafe.Pointer, textLen int, match unsafe.Pointer, nMatch int) int { + return int(C.cre2_set_match(set, textPtr, C.int(textLen), match, C.int(nMatch))) +} + +func SetDelete(ptr unsafe.Pointer) { + C.cre2_set_delete(ptr) +} + func Malloc(size int) unsafe.Pointer { return C.malloc(C.size_t(size)) } diff --git a/internal/re2_re2_cgo.go b/internal/re2_re2_cgo.go index 0b5648c..6245423 100644 --- a/internal/re2_re2_cgo.go +++ b/internal/re2_re2_cgo.go @@ -3,6 +3,7 @@ package internal import ( + "fmt" "unsafe" "github.com/wasilibs/go-re2/internal/cre2" @@ -193,12 +194,17 @@ func newSet(_ *libre2ABI, opts CompileOptions) wasmPtr { return wasmPtr(cre2.NewSet(opt, 0)) } -func setAddSimple(set *Set, s cString) int32 { - return int32(cre2.SetAddSimple(unsafe.Pointer(set.ptr), s.ptr)) -} - -func setAdd(set *Set, s cString, errors wasmPtr, errorsLen int) int32 { - return int32(cre2.SetAdd(unsafe.Pointer(set.ptr), s.ptr, s.length, unsafe.Pointer(errors), errorsLen)) +func setAdd(set *Set, s cString) string { + msgPtr := cre2.SetAdd(unsafe.Pointer(set.ptr), s.ptr, s.length) + if msgPtr == nil { + return unknownCompileError + } + msg := cre2.CopyCString(msgPtr) + if msg != "ok" { + cre2.Free(msgPtr) + return fmt.Sprintf("error parsing regexp: %s", msg) + } + return "" } func setCompile(set *Set) int32 { diff --git a/internal/re2_wazero.go b/internal/re2_wazero.go index 2246321..98a209a 100644 --- a/internal/re2_wazero.go +++ b/internal/re2_wazero.go @@ -8,6 +8,7 @@ import ( _ "embed" "encoding/binary" "errors" + "fmt" "io" "os" "runtime" @@ -61,12 +62,11 @@ type libre2ABI struct { cre2OptSetLatin1Encoding lazyFunction cre2OptSetMaxMem lazyFunction - cre2SetNew lazyFunction - cre2SetAdd lazyFunction - cre2SetAddSimple lazyFunction - cre2SetCompile lazyFunction - cre2SetMatch lazyFunction - cre2SetDelete lazyFunction + cre2SetNew lazyFunction + cre2SetAdd lazyFunction + cre2SetCompile lazyFunction + cre2SetMatch lazyFunction + cre2SetDelete lazyFunction malloc lazyFunction free lazyFunction @@ -233,7 +233,6 @@ func newABI() *libre2ABI { cre2OptSetMaxMem: newLazyFunction("cre2_opt_set_max_mem"), cre2SetNew: newLazyFunction("cre2_set_new"), cre2SetAdd: newLazyFunction("cre2_set_add"), - cre2SetAddSimple: newLazyFunction("cre2_set_add_simple"), cre2SetCompile: newLazyFunction("cre2_set_compile"), cre2SetMatch: newLazyFunction("cre2_set_match"), cre2SetDelete: newLazyFunction("cre2_set_delete"), @@ -495,22 +494,22 @@ func newSet(abi *libre2ABI, opts CompileOptions) wasmPtr { return wasmPtr(res) } -func setAddSimple(set *Set, s cString) int32 { - ctx := context.Background() - res, err := set.abi.cre2SetAddSimple.Call2(ctx, uint64(set.ptr), uint64(s.ptr)) - if err != nil { - panic(err) - } - return int32(res) -} - -func setAdd(set *Set, s cString) wasmPtr { +func setAdd(set *Set, s cString) string { ctx := context.Background() res, err := set.abi.cre2SetAdd.Call3(ctx, uint64(set.ptr), uint64(s.ptr), uint64(s.length)) if err != nil { panic(err) } - return wasmPtr(res) + if res == 0 { + return unknownCompileError + } + msgPtr := wasmPtr(res) + msg := copyCString(wasmPtr(msgPtr)) + if msg != "ok" { + free(set.abi, msgPtr) + return fmt.Sprintf("error parsing regexp: %s", msg) + } + return "" } func setCompile(set *Set) int32 { @@ -621,18 +620,6 @@ func (a *allocation) read(ptr wasmPtr, size int) []byte { return buf } -func readErr(ptr wasmPtr, errorsLen int) string { - buf, ok := wasmMemory.Read(uint32(ptr), uint32(errorsLen)) // Assuming a maximum length of 1024 for the string - if !ok { - panic("failed to read from wasm memory") - } - end := 0 - for end < len(buf) && buf[end] != 0 { - end++ - } - return string(buf[:end]) -} - func (a *allocation) write(b []byte) wasmPtr { ptr := a.allocate(uint32(len(b))) wasmMemory.Write(uint32(ptr), b) diff --git a/internal/set.go b/internal/set.go index bc7cdbc..7994ac7 100644 --- a/internal/set.go +++ b/internal/set.go @@ -2,20 +2,18 @@ package internal import ( "encoding/binary" - "errors" "fmt" "runtime" "sync/atomic" ) -var errErrorUnknown = errors.New("unknown error compiling pattern") - -const errorBufferLength = 64 +const unknownCompileError = "unknown error compiling pattern" type Set struct { ptr wasmPtr abi *libre2ABI opts CompileOptions + exprs []string released uint32 } @@ -23,30 +21,24 @@ func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { abi := newABI() setPtr := newSet(abi, opts) set := &Set{ - ptr: setPtr, - abi: abi, - opts: opts, + ptr: setPtr, + abi: abi, + opts: opts, + exprs: exprs, } - var errorBufferLen int var estimatedMemorySize int for _, expr := range exprs { - errorBufferLen = max(errorBufferLen, len(expr)) - estimatedMemorySize += len(expr) + 2 + 8 + estimatedMemorySize += len(expr) + 2 } - alloc := abi.startOperation(estimatedMemorySize + errorBufferLen + errorBufferLength) + alloc := abi.startOperation(estimatedMemorySize) defer abi.endOperation(alloc) for _, expr := range exprs { cs := alloc.newCString(expr) - msgPtr := setAdd(set, cs) - if msgPtr == 0 { - return nil, errErrorUnknown - } - msg := copyCString(msgPtr) - if msg != "ok" { - free(abi, msgPtr) - return nil, fmt.Errorf("error parsing regexp: %s", msg) + errMsg := setAdd(set, cs) + if errMsg != "" { + return nil, fmt.Errorf("error parsing regexp: %s", errMsg) } } setCompile(set) @@ -57,21 +49,6 @@ func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { return set, nil } -func NewSet(opts CompileOptions) (*Set, error) { - abi := newABI() - setPtr := newSet(abi, opts) - set := &Set{ - ptr: setPtr, - abi: abi, - opts: opts, - } - // Use func(interface{}) form for nottinygc compatibility. - runtime.SetFinalizer(set, func(obj interface{}) { - obj.(*Set).release() - }) - return set, nil -} - func (set *Set) release() { if !atomic.CompareAndSwapUint32(&set.released, 0, 1) { return @@ -79,38 +56,17 @@ func (set *Set) release() { deleteSet(set.abi, set.ptr) } -func (set *Set) Compile(expr string) { - setCompile(set) - runtime.KeepAlive(set) -} - -//func (set *Set) SetAdd(expr string) error { -// alloc := set.abi.startOperation(len(expr) + 2 + 8) -// defer set.abi.endOperation(alloc) -// -// cs := alloc.newCString(expr) -// errorBuffer := alloc.newCStringArray(1) -// defer errorBuffer.free() -// -// if res := setAdd(set, cs, errorBuffer.ptr, errorBufferLength); res == -1 { -// return errors.New(readErrorMessage(&alloc, errorBuffer.ptr, errorBufferLength)) -// } -// -// runtime.KeepAlive(set) -// return nil -//} - -func (set *Set) SetAddSimple(expr string) { - alloc := set.abi.startOperation(len(expr) + 2 + 8) - defer set.abi.endOperation(alloc) - - cs := alloc.newCString(expr) - setAddSimple(set, cs) - - runtime.KeepAlive(set) -} - +// Match executes the Set against the input bytes. It returns a slice +// with the indices of the matched patterns. If n >= 0, it returns at most +// n matches; otherwise, it returns all of them. func (set *Set) Match(b []byte, n int) []int { + if n == 0 { + return nil + } + if n < 0 { + n = len(set.exprs) + } + alloc := set.abi.startOperation(len(b) + 8 + n*8) defer set.abi.endOperation(alloc) From 5e428f1c9204045d35e77c08cf0e9024f127ed4d Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 13:01:46 +0900 Subject: [PATCH 09/14] Rename to FindAll --- experimental/experimental_test.go | 12 ++++++------ internal/set.go | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/experimental/experimental_test.go b/experimental/experimental_test.go index 62cca67..1e42237 100644 --- a/experimental/experimental_test.go +++ b/experimental/experimental_test.go @@ -150,7 +150,7 @@ var setTests = []SetTest{ } func setMatchTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { - m := set.Match([]byte(matchStr), 10) + m := set.FindAll([]byte(matchStr), 10) if !reflect.DeepEqual(m, matchedIds) { t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) } @@ -175,7 +175,7 @@ func BenchmarkSetMatchWithFindSubmatch(b *testing.B) { panic(err) } for i := 0; i < b.N; i++ { - set.Match([]byte("abcd123"), 20) + set.FindAll([]byte("abcd123"), 20) } }) b.Run("findSubmatch", func(b *testing.B) { @@ -195,10 +195,10 @@ func ExampleCompileSet() { if err != nil { panic(err) } - fmt.Println(set.Match([]byte("abcd"), len(exprs))) - fmt.Println(set.Match([]byte("123"), len(exprs))) - fmt.Println(set.Match([]byte("abc123"), len(exprs))) - fmt.Println(set.Match([]byte("def"), len(exprs))) + fmt.Println(set.FindAll([]byte("abcd"), len(exprs))) + fmt.Println(set.FindAll([]byte("123"), len(exprs))) + fmt.Println(set.FindAll([]byte("abc123"), len(exprs))) + fmt.Println(set.FindAll([]byte("def"), len(exprs))) // Output: // [0] // [1] diff --git a/internal/set.go b/internal/set.go index 7994ac7..1499e1d 100644 --- a/internal/set.go +++ b/internal/set.go @@ -56,10 +56,10 @@ func (set *Set) release() { deleteSet(set.abi, set.ptr) } -// Match executes the Set against the input bytes. It returns a slice +// FindAll executes the Set against the input bytes. It returns a slice // with the indices of the matched patterns. If n >= 0, it returns at most // n matches; otherwise, it returns all of them. -func (set *Set) Match(b []byte, n int) []int { +func (set *Set) FindAll(b []byte, n int) []int { if n == 0 { return nil } From 715c4d11703a4e79f5405075affc5c6bddff2f9b Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 13:02:14 +0900 Subject: [PATCH 10/14] Don't export add_simple --- buildtools/wasm/Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/buildtools/wasm/Dockerfile b/buildtools/wasm/Dockerfile index 2542a01..a7e0f4b 100644 --- a/buildtools/wasm/Dockerfile +++ b/buildtools/wasm/Dockerfile @@ -58,7 +58,6 @@ RUN $CXX -o libcre2-noopt.so -Wl,--global-base=1024 $LDFLAGS \ -Wl,--export=cre2_named_groups_iter_next \ -Wl,--export=cre2_named_groups_iter_delete \ -Wl,--export=cre2_set_new \ - -Wl,--export=cre2_set_add_simple \ -Wl,--export=cre2_set_add \ -Wl,--export=cre2_set_match \ -Wl,--export=cre2_set_delete \ From 23ac94715a2d27c801b0568383175c5d4578c56d Mon Sep 17 00:00:00 2001 From: Anuraag Agrawal Date: Wed, 18 Dec 2024 13:24:05 +0900 Subject: [PATCH 11/14] Lint --- internal/re2_re2_cgo.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/re2_re2_cgo.go b/internal/re2_re2_cgo.go index 6245423..858ecd7 100644 --- a/internal/re2_re2_cgo.go +++ b/internal/re2_re2_cgo.go @@ -117,10 +117,6 @@ func (a *allocation) read(ptr wasmPtr, size int) []byte { return (*[1 << 30]byte)(unsafe.Pointer(ptr))[:size:size] } -func readErr(ptr wasmPtr, _ int) string { - return cre2.CopyCString(unsafe.Pointer(ptr)) -} - type cString struct { ptr unsafe.Pointer length int From a70e097fcdb10fa0e454277b0d900c60c90fb9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Thu, 19 Dec 2024 15:35:48 +0800 Subject: [PATCH 12/14] Add String version of the find method and test cases for various n format --- experimental/experimental_test.go | 129 +++++++++++++++++++++--------- internal/set.go | 35 +++++++- 2 files changed, 126 insertions(+), 38 deletions(-) diff --git a/experimental/experimental_test.go b/experimental/experimental_test.go index 1e42237..9c2d9a0 100644 --- a/experimental/experimental_test.go +++ b/experimental/experimental_test.go @@ -3,6 +3,7 @@ package experimental import ( "fmt" "reflect" + "sort" "strings" "testing" @@ -86,22 +87,18 @@ type stringError struct { } var badSet = []stringError{ - {`*`, "no argument for repetition operator: *"}, - {`+`, "no argument for repetition operator: +"}, - {`?`, "no argument for repetition operator: ?"}, - {`(abc`, "missing ): (abc"}, - {`abc)`, "unexpected ): abc)"}, - {`x[a-z`, "missing ]: [a-z"}, - {`[z-a]`, "invalid character class range: z-a"}, - {`abc\`, "trailing \\"}, - {`a**`, "bad repetition operator: **"}, - {`a*+`, "bad repetition operator: *+"}, - {`\x`, "invalid escape sequence: \\x"}, - {strings.Repeat(`)\pL`, 27000), "unexpected ): " + strings.Repeat(`)\pL`, 27000)}, -} - -func TestGoodSetCompile(t *testing.T) { - compileSetTest(t, goodRe, "") + {`*`, "error parsing regexp: no argument for repetition operator: *"}, + {`+`, "error parsing regexp: no argument for repetition operator: +"}, + {`?`, "error parsing regexp: no argument for repetition operator: ?"}, + {`(abc`, "error parsing regexp: missing ): (abc"}, + {`abc)`, "error parsing regexp: unexpected ): abc)"}, + {`x[a-z`, "error parsing regexp: missing ]: [a-z"}, + {`[z-a]`, "error parsing regexp: invalid character class range: z-a"}, + {`abc\`, "error parsing regexp: trailing \\"}, + {`a**`, "error parsing regexp: bad repetition operator: **"}, + {`a*+`, "error parsing regexp: bad repetition operator: *+"}, + {`\x`, "error parsing regexp: invalid escape sequence: \\x"}, + {strings.Repeat(`)\pL`, 27000), "error parsing regexp: unexpected ): " + strings.Repeat(`)\pL`, 27000)}, } func compileSetTest(t *testing.T, exprs []string, error string) *Set { @@ -117,6 +114,10 @@ func compileSetTest(t *testing.T, exprs []string, error string) *Set { return set } +func TestGoodSetCompile(t *testing.T) { + compileSetTest(t, goodRe, "") +} + func TestBadCompileSet(t *testing.T) { for i := 0; i < len(badSet); i++ { compileSetTest(t, []string{badSet[i].re}, badSet[i].err) @@ -124,50 +125,104 @@ func TestBadCompileSet(t *testing.T) { } type SetTest struct { - expr []string - matches map[string][]int + exprs []string + matches string + matched [4][]int } var setTests = []SetTest{ { - expr: []string{"abc", "\\d+"}, - matches: map[string][]int{ - "abc": {0}, - "123": {1}, - "abc123": {0, 1}, - "def": {}, + exprs: []string{`(d)(e){0}(f)`, `[a-c]+`, `abc`, `\d+`}, + matches: "x", + matched: [4][]int{ + nil, {}, {}, {}, + }, + }, + { + exprs: []string{`(d)(e){0}(f)`, `[a-c]+`, `abc`, `\d+`}, + matches: "123", + matched: [4][]int{ + nil, {3}, {3}, {3}, + }, + }, + { + exprs: []string{`(d)(e){0}(f)`, `[a-c]+`, `abc`, `\d+`}, + matches: "df123abc", + matched: [4][]int{ + nil, {0}, {0, 3}, {0, 1, 2, 3}, + }, + }, + { + exprs: []string{`(d)(e){0}(f)`, `[a-c]+`, `abc`, `\d+`, `d{4}-\d{2}-\d{2}$`, `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`, `1[3-9]\d{9}`, `\.[a-zA-Z0-9]+$`, ``}, + matches: "abcdef12313988889181demo@gmail.com", + matched: [4][]int{ + nil, {1}, {1, 2}, {1, 2, 3, 5, 6, 7, 8}, }, }, { - expr: []string{"[a-c]+", "(d)(e){0}(f)"}, - matches: map[string][]int{ - "a234v": {0}, - "df": {1}, - "abcdf": {0, 1}, - "def": {}, + exprs: []string{`(d)(e){0}(f)`, `[a-c]+`, `abc`, `\d+`, `d{4}-\d{2}-\d{2}$`, `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`, `1[3-9]\d{9}`, `\.[a-zA-Z0-9]+$`, ``}, + matches: "df12313988889181demo@gmail.com", + matched: [4][]int{ + nil, {0}, {0, 3}, {0, 1, 3, 5, 6, 7}, }, }, } -func setMatchTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { - m := set.FindAll([]byte(matchStr), 10) +func setFindAllTest(t *testing.T, set *Set, matchStr string, matchNum int, matchedIds []int) { + m := set.FindAll([]byte(matchStr), matchNum) + sort.Ints(m) if !reflect.DeepEqual(m, matchedIds) { t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) } } -func TestSetMatch(t *testing.T) { +func setFindTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { + m := set.Find([]byte(matchStr)) + match := make([]int, 0) + if m >= 0 { + match = []int{m} + } + if !reflect.DeepEqual(match, matchedIds) { + t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) + } +} + +func TestSetFindAll(t *testing.T) { for _, test := range setTests { - set := compileSetTest(t, test.expr, "") + set := compileSetTest(t, test.exprs, "") if set == nil { return } - for matchStr, matchedIds := range test.matches { - setMatchTest(t, set, matchStr, matchedIds) - } + setFindAllTest(t, set, test.matches, 0, test.matched[0]) + setFindTest(t, set, test.matches, test.matched[1]) + setFindAllTest(t, set, test.matches, 1, test.matched[1]) + setFindAllTest(t, set, test.matches, 2, test.matched[2]) + setFindAllTest(t, set, test.matches, 7, test.matched[3]) + setFindAllTest(t, set, test.matches, 20, test.matched[3]) } } +func BenchmarkSet(b *testing.B) { + b.Run("find", func(b *testing.B) { + set, err := CompileSet(goodRe) + if err != nil { + panic(err) + } + for i := 0; i < b.N; i++ { + set.Find([]byte("abcdef12313988889181demo@gmail.com")) + } + }) + b.Run("findAll", func(b *testing.B) { + set, err := CompileSet(goodRe) + if err != nil { + panic(err) + } + for i := 0; i < b.N; i++ { + set.FindAll([]byte("abcdef12313988889181demo@gmail.com"), 20) + } + }) +} + func BenchmarkSetMatchWithFindSubmatch(b *testing.B) { b.Run("set match", func(b *testing.B) { set, err := CompileSet(goodRe) diff --git a/internal/set.go b/internal/set.go index 1499e1d..31bd83c 100644 --- a/internal/set.go +++ b/internal/set.go @@ -38,7 +38,7 @@ func CompileSet(exprs []string, opts CompileOptions) (*Set, error) { cs := alloc.newCString(expr) errMsg := setAdd(set, cs) if errMsg != "" { - return nil, fmt.Errorf("error parsing regexp: %s", errMsg) + return nil, fmt.Errorf("%s", errMsg) } } setCompile(set) @@ -56,6 +56,39 @@ func (set *Set) release() { deleteSet(set.abi, set.ptr) } +// Find searches for the first occurrence of any pattern in the Set within the given byte slice. +// It returns the index of the matched pattern or -1 if no match is found. +// +// Parameters: +// - b: The byte slice to search within. +// +// Returns: +// - int: The index of the matched pattern, or -1 if no match is found. +func (set *Set) Find(b []byte) int { + if len(b) == 0 { + return -1 + } + + alloc := set.abi.startOperation(len(b) + 8) + defer set.abi.endOperation(alloc) + + cs := alloc.newCStringFromBytes(b) + matchArr := alloc.newCStringArray(1) + defer matchArr.free() + + matchedCount := setMatch(set, cs, matchArr.ptr, 1) + if matchedCount == 0 { + return -1 + } + + matches := alloc.read(matchArr.ptr, 4) + matchedID := int(binary.LittleEndian.Uint32(matches)) + + runtime.KeepAlive(b) + runtime.KeepAlive(set) // don't allow finalizer to run during method + return matchedID +} + // FindAll executes the Set against the input bytes. It returns a slice // with the indices of the matched patterns. If n >= 0, it returns at most // n matches; otherwise, it returns all of them. From 5b052a6ff81c8cc4797b661a19d90671c7fbcc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Fri, 20 Dec 2024 17:09:10 +0800 Subject: [PATCH 13/14] Remove Find and add FindAllString --- experimental/experimental_test.go | 37 +++++++++--------- internal/set.go | 64 ++++++++++++++----------------- 2 files changed, 48 insertions(+), 53 deletions(-) diff --git a/experimental/experimental_test.go b/experimental/experimental_test.go index 9c2d9a0..c024785 100644 --- a/experimental/experimental_test.go +++ b/experimental/experimental_test.go @@ -135,7 +135,7 @@ var setTests = []SetTest{ exprs: []string{`(d)(e){0}(f)`, `[a-c]+`, `abc`, `\d+`}, matches: "x", matched: [4][]int{ - nil, {}, {}, {}, + nil, nil, nil, nil, }, }, { @@ -176,13 +176,10 @@ func setFindAllTest(t *testing.T, set *Set, matchStr string, matchNum int, match } } -func setFindTest(t *testing.T, set *Set, matchStr string, matchedIds []int) { - m := set.Find([]byte(matchStr)) - match := make([]int, 0) - if m >= 0 { - match = []int{m} - } - if !reflect.DeepEqual(match, matchedIds) { +func setFindAllStringTest(t *testing.T, set *Set, matchStr string, matchNum int, matchedIds []int) { + m := set.FindAllString(matchStr, matchNum) + sort.Ints(m) + if !reflect.DeepEqual(m, matchedIds) { t.Errorf("Match failure on %s: %v should be %v", matchStr, m, matchedIds) } } @@ -194,7 +191,6 @@ func TestSetFindAll(t *testing.T) { return } setFindAllTest(t, set, test.matches, 0, test.matched[0]) - setFindTest(t, set, test.matches, test.matched[1]) setFindAllTest(t, set, test.matches, 1, test.matched[1]) setFindAllTest(t, set, test.matches, 2, test.matched[2]) setFindAllTest(t, set, test.matches, 7, test.matched[3]) @@ -202,16 +198,21 @@ func TestSetFindAll(t *testing.T) { } } -func BenchmarkSet(b *testing.B) { - b.Run("find", func(b *testing.B) { - set, err := CompileSet(goodRe) - if err != nil { - panic(err) - } - for i := 0; i < b.N; i++ { - set.Find([]byte("abcdef12313988889181demo@gmail.com")) +func TestSetFindAllString(t *testing.T) { + for _, test := range setTests { + set := compileSetTest(t, test.exprs, "") + if set == nil { + return } - }) + setFindAllStringTest(t, set, test.matches, 0, test.matched[0]) + setFindAllStringTest(t, set, test.matches, 1, test.matched[1]) + setFindAllStringTest(t, set, test.matches, 2, test.matched[2]) + setFindAllStringTest(t, set, test.matches, 7, test.matched[3]) + setFindAllStringTest(t, set, test.matches, 20, test.matched[3]) + } +} + +func BenchmarkSet(b *testing.B) { b.Run("findAll", func(b *testing.B) { set, err := CompileSet(goodRe) if err != nil { diff --git a/internal/set.go b/internal/set.go index 31bd83c..0e0daa7 100644 --- a/internal/set.go +++ b/internal/set.go @@ -56,37 +56,23 @@ func (set *Set) release() { deleteSet(set.abi, set.ptr) } -// Find searches for the first occurrence of any pattern in the Set within the given byte slice. -// It returns the index of the matched pattern or -1 if no match is found. -// -// Parameters: -// - b: The byte slice to search within. -// -// Returns: -// - int: The index of the matched pattern, or -1 if no match is found. -func (set *Set) Find(b []byte) int { - if len(b) == 0 { - return -1 +// FindAllString finds all matches of the regular expressions in the Set against the input string. +// It returns a slice of indices of the matched patterns. If n >= 0, it returns at most n matches; otherwise, it returns all of them. +func (set *Set) FindAllString(s string, n int) []int { + if n == 0 { + return nil } - - alloc := set.abi.startOperation(len(b) + 8) + alloc := set.abi.startOperation(len(s) + 8 + n*8) defer set.abi.endOperation(alloc) - cs := alloc.newCStringFromBytes(b) - matchArr := alloc.newCStringArray(1) - defer matchArr.free() - - matchedCount := setMatch(set, cs, matchArr.ptr, 1) - if matchedCount == 0 { - return -1 - } + cs := alloc.newCString(s) - matches := alloc.read(matchArr.ptr, 4) - matchedID := int(binary.LittleEndian.Uint32(matches)) + var matches []int - runtime.KeepAlive(b) - runtime.KeepAlive(set) // don't allow finalizer to run during method - return matchedID + set.findAll(&alloc, cs, n, func(match int) { + matches = append(matches, match) + }) + return matches } // FindAll executes the Set against the input bytes. It returns a slice @@ -96,26 +82,34 @@ func (set *Set) FindAll(b []byte, n int) []int { if n == 0 { return nil } + alloc := set.abi.startOperation(len(b) + 8 + n*8) + defer set.abi.endOperation(alloc) + + cs := alloc.newCStringFromBytes(b) + + var matches []int + + set.findAll(&alloc, cs, n, func(match int) { + matches = append(matches, match) + }) + + return matches +} + +func (set *Set) findAll(alloc *allocation, cs cString, n int, deliver func(match int)) { if n < 0 { n = len(set.exprs) } - alloc := set.abi.startOperation(len(b) + 8 + n*8) - defer set.abi.endOperation(alloc) - matchArr := alloc.newCStringArray(n) defer matchArr.free() - cs := alloc.newCStringFromBytes(b) matchedCount := setMatch(set, cs, matchArr.ptr, n) - matchedIDs := make([]int, min(matchedCount, n)) matches := alloc.read(matchArr.ptr, n*4) - for i := 0; i < len(matchedIDs); i++ { - matchedIDs[i] = int(binary.LittleEndian.Uint32(matches[i*4:])) + for i := 0; i < matchedCount && i < n; i++ { + deliver(int(binary.LittleEndian.Uint32(matches[i*4:]))) } - runtime.KeepAlive(b) runtime.KeepAlive(matchArr) runtime.KeepAlive(set) // don't allow finalizer to run during method - return matchedIDs } From d7db17c12a0ed01437e973920af2321b9e7c9fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B1=9F=20=E6=9D=A8?= Date: Mon, 23 Dec 2024 17:14:35 +0800 Subject: [PATCH 14/14] Fix not enough reserved shared memory --- internal/set.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/set.go b/internal/set.go index 0e0daa7..ab4900d 100644 --- a/internal/set.go +++ b/internal/set.go @@ -62,6 +62,9 @@ func (set *Set) FindAllString(s string, n int) []int { if n == 0 { return nil } + if n < 0 { + n = len(set.exprs) + } alloc := set.abi.startOperation(len(s) + 8 + n*8) defer set.abi.endOperation(alloc) @@ -82,6 +85,9 @@ func (set *Set) FindAll(b []byte, n int) []int { if n == 0 { return nil } + if n < 0 { + n = len(set.exprs) + } alloc := set.abi.startOperation(len(b) + 8 + n*8) defer set.abi.endOperation(alloc) @@ -97,10 +103,6 @@ func (set *Set) FindAll(b []byte, n int) []int { } func (set *Set) findAll(alloc *allocation, cs cString, n int, deliver func(match int)) { - if n < 0 { - n = len(set.exprs) - } - matchArr := alloc.newCStringArray(n) defer matchArr.free()