Skip to content

Commit ae26f64

Browse files
authored
add windows compat with PCRE2_BUILD_FROM_SOURCE=1 control (#10)
* add windows compat with PCRE2_BUILD_FROM_SOURCE=1 control * update docs * fix compile * fix compile * fix version * fix windows linking * fix windows compile * fix windows compile2 * compile for utf16 * revert to use pcre2-8 utf8
1 parent 31aaf1c commit ae26f64

6 files changed

Lines changed: 420 additions & 27 deletions

File tree

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,28 @@ exposing PCRE2’s extended flag set through the Pythonic `Flag` enum
5757
- `pcre.escape()` delegates directly to `re.escape` for byte and text
5858
patterns so escaping semantics remain identical.
5959

60+
### `regex` package compatibility
61+
62+
The [`regex`](https://pypi.org/project/regex/) package interprets both
63+
`\uXXXX`/`\u{...}` and `\UXXXXXXXX` escapes as UTF-8 code points, while
64+
PCRE2 expects hexadecimal escapes to use the `\x{...}` form. Enable
65+
`Flag.COMPAT_REGEX` to translate those escapes automatically when compiling
66+
patterns:
67+
68+
```python
69+
from pcre import compile, Flag
70+
71+
pattern = compile(r"\\u{1F600}", flags=Flag.COMPAT_REGEX)
72+
assert pattern.pattern == r"\\x{1F600}"
73+
```
74+
75+
Set the default behaviour globally with `pcre.configure(compat_regex=True)`
76+
so that subsequent calls to `compile()` and the module-level helpers apply
77+
the conversion without repeating the flag.
78+
6079
### Automatic pattern caching
6180

62-
`pcre.compile()` caches the final `Pattern` wrapper for up to 2048
81+
`pcre.compile()` caches the final `Pattern` wrapper for up to 128
6382
unique `(pattern, flags)` pairs when the pattern object is hashable. This
6483
keeps repeated calls to top-level helpers efficient without any extra work
6584
from the caller. Adjust the capacity with `pcre.set_cache_limit(n)`—pass
@@ -168,7 +187,7 @@ location.
168187
# Notes
169188

170189
## Pattern cache
171-
- `pcre.compile()` caches hashable `(pattern, flags)` pairs, keeping up to 2048 entries.
190+
- `pcre.compile()` caches hashable `(pattern, flags)` pairs, keeping up to 128 entries.
172191
- Use `pcre.clear_cache()` when you need to free the cache proactively.
173192
- Non-hashable pattern objects skip the cache and are compiled each time.
174193

@@ -179,12 +198,12 @@ location.
179198

180199
## Additional usage notes
181200
- All top-level helpers (`match`, `search`, `fullmatch`, `finditer`, `findall`) defer to the cached compiler.
182-
- Compiled `Pattern` objects expose `.pattern`, `.pattern_bytes`, `.flags`, and `.groupindex` for introspection.
201+
- Compiled `Pattern` objects expose `.pattern`, `.flags`, `.jit`, and `.groupindex` for introspection.
183202
- Execution helpers accept `pos`, `endpos`, and `options`, allowing you to thread PCRE2 execution flags per call.
184203

185204
## Memory allocation
186205
- The extension selects the fastest available allocator at import time: it
187-
prefers tcmalloc, then jemalloc, and finally falls back to the platform
206+
prefers jemalloc, then tcmalloc, and finally falls back to the platform
188207
`malloc`. Optional allocators are loaded via `dlopen`, so no additional
189208
link flags are required when they are absent.
190209
- All internal buffers (match data wrappers, JIT stack cache entries, error

pcre_ext/pcre2.c

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,31 @@
1010
#include <immintrin.h>
1111
#endif
1212

13+
#define STRINGIFY_DETAIL(value) #value
14+
#define STRINGIFY(value) STRINGIFY_DETAIL(value)
15+
16+
static const char *
17+
resolve_pcre2_prerelease(void)
18+
{
19+
const char *raw = STRINGIFY(Z PCRE2_PRERELEASE);
20+
21+
if (raw[1] == '\0') {
22+
return "";
23+
}
24+
25+
raw += 1;
26+
while (*raw == ' ') {
27+
raw++;
28+
}
29+
30+
return raw;
31+
}
32+
1333
static int default_jit_enabled = 1;
1434
static PyThread_type_lock default_jit_lock = NULL;
1535
static PyThread_type_lock cpu_feature_lock = NULL;
36+
static char pcre2_library_version[64] = "unknown";
37+
static int pcre2_version_initialized = 0;
1638
#if defined(PCRE2_USE_OFFSET_LIMIT)
1739
static int offset_limit_support = -1;
1840
#endif
@@ -1871,6 +1893,8 @@ module_configure(PyObject *Py_UNUSED(module), PyObject *args, PyObject *kwargs)
18711893

18721894
static PyObject *module_cpu_ascii_vector_mode(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args));
18731895
static PyObject *module_memory_allocator(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args));
1896+
static PyObject *module_get_pcre2_version(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args));
1897+
static void initialize_pcre2_version(void);
18741898

18751899
static PyMethodDef module_methods[] = {
18761900
{"compile", (PyCFunction)module_compile, METH_VARARGS | METH_KEYWORDS, PyDoc_STR("Compile a pattern into a PCRE2 Pattern object." )},
@@ -1888,6 +1912,7 @@ static PyMethodDef module_methods[] = {
18881912
{"get_jit_stack_cache_count", (PyCFunction)module_get_jit_stack_cache_count, METH_NOARGS, PyDoc_STR("Return the number of cached JIT stacks currently stored." )},
18891913
{"get_jit_stack_limits", (PyCFunction)module_get_jit_stack_limits, METH_NOARGS, PyDoc_STR("Return the configured (start, max) JIT stack sizes." )},
18901914
{"set_jit_stack_limits", (PyCFunction)module_set_jit_stack_limits, METH_VARARGS, PyDoc_STR("Set the (start, max) sizes for newly created JIT stacks." )},
1915+
{"get_library_version", (PyCFunction)module_get_pcre2_version, METH_NOARGS, PyDoc_STR("Return the PCRE2 library version string." )},
18911916
{"get_allocator", (PyCFunction)module_memory_allocator, METH_NOARGS, PyDoc_STR("Return the name of the active heap allocator (tcmalloc/jemalloc/malloc)." )},
18921917
{"_cpu_ascii_vector_mode", (PyCFunction)module_cpu_ascii_vector_mode, METH_NOARGS, PyDoc_STR("Return the active ASCII vector width (0=scalar,1=SSE2,2=AVX2,3=AVX512)." )},
18931918
{NULL, NULL, 0, NULL},
@@ -2023,6 +2048,12 @@ PyInit_cpcre2(void)
20232048
goto error;
20242049
}
20252050

2051+
initialize_pcre2_version();
2052+
2053+
if (PyModule_AddStringConstant(module, "PCRE2_VERSION", pcre2_library_version) < 0) {
2054+
goto error;
2055+
}
2056+
20262057
if (PyModule_AddStringConstant(module, "__version__", "0.1.0") < 0) {
20272058
goto error;
20282059
}
@@ -2071,3 +2102,45 @@ module_memory_allocator(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
20712102
const char *name = pcre_memory_allocator_name();
20722103
return PyUnicode_FromString(name);
20732104
}
2105+
2106+
static PyObject *
2107+
module_get_pcre2_version(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
2108+
{
2109+
initialize_pcre2_version();
2110+
return PyUnicode_FromString(pcre2_library_version);
2111+
}
2112+
2113+
static void
2114+
initialize_pcre2_version(void)
2115+
{
2116+
if (pcre2_version_initialized) {
2117+
return;
2118+
}
2119+
2120+
char buffer[sizeof(pcre2_library_version)] = {0};
2121+
if (pcre2_config(PCRE2_CONFIG_VERSION, buffer) == 0 && buffer[0] != '\0') {
2122+
strncpy(pcre2_library_version, buffer, sizeof(pcre2_library_version) - 1);
2123+
pcre2_library_version[sizeof(pcre2_library_version) - 1] = '\0';
2124+
} else {
2125+
const char *pre_release = resolve_pcre2_prerelease();
2126+
if (pre_release[0] != '\0') {
2127+
(void)snprintf(
2128+
pcre2_library_version,
2129+
sizeof(pcre2_library_version),
2130+
"%d.%d-%s",
2131+
PCRE2_MAJOR,
2132+
PCRE2_MINOR,
2133+
pre_release
2134+
);
2135+
} else {
2136+
(void)snprintf(
2137+
pcre2_library_version,
2138+
sizeof(pcre2_library_version),
2139+
"%d.%d",
2140+
PCRE2_MAJOR,
2141+
PCRE2_MINOR
2142+
);
2143+
}
2144+
}
2145+
pcre2_version_initialized = 1;
2146+
}

pcre_ext/pcre2.h

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ POSSIBILITY OF SUCH DAMAGE.
4242
/* The current PCRE version information. */
4343

4444
#define PCRE2_MAJOR 10
45-
#define PCRE2_MINOR 47
46-
#define PCRE2_PRERELEASE -DEV
45+
#define PCRE2_MINOR 46
46+
#define PCRE2_PRERELEASE
4747
#define PCRE2_DATE 2025-08-27
4848

4949
/* When an application links to a PCRE DLL in Windows, the symbols that are
@@ -343,10 +343,6 @@ pcre2_pattern_convert(). */
343343
#define PCRE2_ERROR_PERL_ECLASS_EMPTY_EXPR 214
344344
#define PCRE2_ERROR_PERL_ECLASS_MISSING_CLOSE 215
345345
#define PCRE2_ERROR_PERL_ECLASS_UNEXPECTED_CHAR 216
346-
#define PCRE2_ERROR_EXPECTED_CAPTURE_GROUP 217
347-
#define PCRE2_ERROR_MISSING_OPENING_PARENTHESIS 218
348-
#define PCRE2_ERROR_MISSING_NUMBER_TERMINATOR 219
349-
#define PCRE2_ERROR_NULL_ERROROFFSET 220
350346

351347
/* "Expected" matching error codes: no match and partial match. */
352348

@@ -436,10 +432,6 @@ released, the numbers must not be changed. */
436432
#define PCRE2_ERROR_JIT_UNSUPPORTED (-68)
437433
#define PCRE2_ERROR_REPLACECASE (-69)
438434
#define PCRE2_ERROR_TOOLARGEREPLACE (-70)
439-
#define PCRE2_ERROR_DIFFSUBSPATTERN (-71)
440-
#define PCRE2_ERROR_DIFFSUBSSUBJECT (-72)
441-
#define PCRE2_ERROR_DIFFSUBSOFFSET (-73)
442-
#define PCRE2_ERROR_DIFFSUBSOPTIONS (-74)
443435

444436

445437
/* Request types for pcre2_pattern_info() */
@@ -492,7 +484,6 @@ released, the numbers must not be changed. */
492484
#define PCRE2_CONFIG_NEVER_BACKSLASH_C 13
493485
#define PCRE2_CONFIG_COMPILED_WIDTHS 14
494486
#define PCRE2_CONFIG_TABLES_LENGTH 15
495-
#define PCRE2_CONFIG_EFFECTIVE_LINKSIZE 16
496487

497488
/* Optimization directives for pcre2_set_optimize().
498489
For binary compatibility, only add to this list; do not renumber. */
@@ -752,14 +743,14 @@ PCRE2_EXP_DECL pcre2_match_data *PCRE2_CALL_CONVENTION \
752743
PCRE2_EXP_DECL pcre2_match_data *PCRE2_CALL_CONVENTION \
753744
pcre2_match_data_create_from_pattern(const pcre2_code *, \
754745
pcre2_general_context *); \
755-
PCRE2_EXP_DECL void PCRE2_CALL_CONVENTION \
756-
pcre2_match_data_free(pcre2_match_data *); \
757746
PCRE2_EXP_DECL int PCRE2_CALL_CONVENTION \
758747
pcre2_dfa_match(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, \
759748
uint32_t, pcre2_match_data *, pcre2_match_context *, int *, PCRE2_SIZE); \
760749
PCRE2_EXP_DECL int PCRE2_CALL_CONVENTION \
761750
pcre2_match(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, \
762751
uint32_t, pcre2_match_data *, pcre2_match_context *); \
752+
PCRE2_EXP_DECL void PCRE2_CALL_CONVENTION \
753+
pcre2_match_data_free(pcre2_match_data *); \
763754
PCRE2_EXP_DECL PCRE2_SPTR PCRE2_CALL_CONVENTION \
764755
pcre2_get_mark(pcre2_match_data *); \
765756
PCRE2_EXP_DECL PCRE2_SIZE PCRE2_CALL_CONVENTION \
@@ -771,9 +762,7 @@ PCRE2_EXP_DECL uint32_t PCRE2_CALL_CONVENTION \
771762
PCRE2_EXP_DECL PCRE2_SIZE *PCRE2_CALL_CONVENTION \
772763
pcre2_get_ovector_pointer(pcre2_match_data *); \
773764
PCRE2_EXP_DECL PCRE2_SIZE PCRE2_CALL_CONVENTION \
774-
pcre2_get_startchar(pcre2_match_data *); \
775-
PCRE2_EXP_DECL int PCRE2_CALL_CONVENTION \
776-
pcre2_next_match(pcre2_match_data *, PCRE2_SIZE *, uint32_t *);
765+
pcre2_get_startchar(pcre2_match_data *);
777766

778767

779768
/* Convenience functions for handling matched substrings. */
@@ -953,7 +942,6 @@ pcre2_compile are called by application code. */
953942
#define pcre2_match_data_create PCRE2_SUFFIX(pcre2_match_data_create_)
954943
#define pcre2_match_data_create_from_pattern PCRE2_SUFFIX(pcre2_match_data_create_from_pattern_)
955944
#define pcre2_match_data_free PCRE2_SUFFIX(pcre2_match_data_free_)
956-
#define pcre2_next_match PCRE2_SUFFIX(pcre2_next_match_)
957945
#define pcre2_pattern_convert PCRE2_SUFFIX(pcre2_pattern_convert_)
958946
#define pcre2_pattern_info PCRE2_SUFFIX(pcre2_pattern_info_)
959947
#define pcre2_serialize_decode PCRE2_SUFFIX(pcre2_serialize_decode_)

pcre_ext/util.c

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@
55

66
#include "pcre2_module.h"
77
#include <string.h>
8+
#include <stdint.h>
9+
10+
#if defined(_MSC_VER)
11+
static inline unsigned int
12+
popcountll(uint64_t value)
13+
{
14+
value -= (value >> 1) & 0x5555555555555555ULL;
15+
value = (value & 0x3333333333333333ULL) + ((value >> 2) & 0x3333333333333333ULL);
16+
value = (value + (value >> 4)) & 0x0F0F0F0F0F0F0F0FULL;
17+
return (unsigned int)((value * 0x0101010101010101ULL) >> 56);
18+
}
19+
#else
20+
static inline unsigned int
21+
popcountll(uint64_t value)
22+
{
23+
return (unsigned int)__builtin_popcountll(value);
24+
}
25+
#endif
826

927
PyObject *
1028
bytes_from_text(PyObject *obj)
@@ -75,7 +93,7 @@ utf8_index_to_offset(PyObject *unicode_obj, Py_ssize_t index, Py_ssize_t *offset
7593
for (Py_ssize_t i = 0; i < fast_chunks; ++i) {
7694
uint64_t block;
7795
memcpy(&block, ptr, sizeof(uint64_t));
78-
non_ascii += __builtin_popcountll(block & high_bit_mask);
96+
non_ascii += popcountll(block & high_bit_mask);
7997
ptr += chunk;
8098
}
8199

0 commit comments

Comments
 (0)