| // Copyright 2019 The RE2 Authors. All Rights Reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| #include <stddef.h> | |
| #include <sys/types.h> | |
| #include <memory> | |
| #include <stdexcept> | |
| #include <string> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include "absl/strings/string_view.h" | |
| #include "pybind11/buffer_info.h" | |
| #include "pybind11/gil.h" | |
| #include "pybind11/pybind11.h" | |
| #include "pybind11/pytypes.h" | |
| #include "pybind11/stl.h" // IWYU pragma: keep | |
| #include "re2/filtered_re2.h" | |
| #include "re2/re2.h" | |
| #include "re2/set.h" | |
| #ifdef _WIN32 | |
| #include <basetsd.h> | |
| #define ssize_t SSIZE_T | |
| #endif | |
| namespace re2_python { | |
| // This is conventional. | |
| namespace py = pybind11; | |
| // In terms of the pybind11 API, a py::buffer is merely a py::object that | |
| // supports the buffer interface/protocol and you must explicitly request | |
| // a py::buffer_info in order to access the actual bytes. Under the hood, | |
| // the py::buffer_info manages a reference count to the py::buffer, so it | |
| // must be constructed and subsequently destructed while holding the GIL. | |
| static inline absl::string_view FromBytes(const py::buffer_info& bytes) { | |
| char* data = reinterpret_cast<char*>(bytes.ptr); | |
| ssize_t size = bytes.size; | |
| return absl::string_view(data, size); | |
| } | |
| static inline int OneCharLen(const char* ptr) { | |
| return "\1\1\1\1\1\1\1\1\1\1\1\1\2\2\3\4"[(*ptr & 0xFF) >> 4]; | |
| } | |
| // Helper function for when Python encodes str to bytes and then needs to | |
| // convert str offsets to bytes offsets. Assumes that text is valid UTF-8. | |
| ssize_t CharLenToBytes(py::buffer buffer, ssize_t pos, ssize_t len) { | |
| auto bytes = buffer.request(); | |
| auto text = FromBytes(bytes); | |
| auto ptr = text.data() + pos; | |
| auto end = text.data() + text.size(); | |
| while (ptr < end && len > 0) { | |
| ptr += OneCharLen(ptr); | |
| --len; | |
| } | |
| return ptr - (text.data() + pos); | |
| } | |
| // Helper function for when Python decodes bytes to str and then needs to | |
| // convert bytes offsets to str offsets. Assumes that text is valid UTF-8. | |
| ssize_t BytesToCharLen(py::buffer buffer, ssize_t pos, ssize_t endpos) { | |
| auto bytes = buffer.request(); | |
| auto text = FromBytes(bytes); | |
| auto ptr = text.data() + pos; | |
| auto end = text.data() + endpos; | |
| ssize_t len = 0; | |
| while (ptr < end) { | |
| ptr += OneCharLen(ptr); | |
| ++len; | |
| } | |
| return len; | |
| } | |
| std::unique_ptr<RE2> RE2InitShim(py::buffer buffer, | |
| const RE2::Options& options) { | |
| auto bytes = buffer.request(); | |
| auto pattern = FromBytes(bytes); | |
| return std::make_unique<RE2>(pattern, options); | |
| } | |
| py::bytes RE2ErrorShim(const RE2& self) { | |
| // Return std::string as bytes. That is, without decoding to str. | |
| return self.error(); | |
| } | |
| std::vector<std::pair<py::bytes, int>> RE2NamedCapturingGroupsShim( | |
| const RE2& self) { | |
| const int num_groups = self.NumberOfCapturingGroups(); | |
| std::vector<std::pair<py::bytes, int>> groups; | |
| groups.reserve(num_groups); | |
| for (const auto& it : self.NamedCapturingGroups()) { | |
| groups.emplace_back(it.first, it.second); | |
| } | |
| return groups; | |
| } | |
| std::vector<int> RE2ProgramFanoutShim(const RE2& self) { | |
| std::vector<int> histogram; | |
| self.ProgramFanout(&histogram); | |
| return histogram; | |
| } | |
| std::vector<int> RE2ReverseProgramFanoutShim(const RE2& self) { | |
| std::vector<int> histogram; | |
| self.ReverseProgramFanout(&histogram); | |
| return histogram; | |
| } | |
| std::tuple<bool, py::bytes, py::bytes> RE2PossibleMatchRangeShim( | |
| const RE2& self, int maxlen) { | |
| std::string min, max; | |
| // Return std::string as bytes. That is, without decoding to str. | |
| return {self.PossibleMatchRange(&min, &max, maxlen), min, max}; | |
| } | |
| std::vector<std::pair<ssize_t, ssize_t>> RE2MatchShim(const RE2& self, | |
| RE2::Anchor anchor, | |
| py::buffer buffer, | |
| ssize_t pos, | |
| ssize_t endpos) { | |
| auto bytes = buffer.request(); | |
| auto text = FromBytes(bytes); | |
| const int num_groups = self.NumberOfCapturingGroups() + 1; // need $0 | |
| std::vector<absl::string_view> groups; | |
| groups.resize(num_groups); | |
| py::gil_scoped_release release_gil; | |
| if (!self.Match(text, pos, endpos, anchor, groups.data(), groups.size())) { | |
| // Ensure that groups are null before converting to spans! | |
| for (auto& it : groups) { | |
| it = absl::string_view(); | |
| } | |
| } | |
| std::vector<std::pair<ssize_t, ssize_t>> spans; | |
| spans.reserve(num_groups); | |
| for (const auto& it : groups) { | |
| if (it.data() == NULL) { | |
| spans.emplace_back(-1, -1); | |
| } else { | |
| spans.emplace_back(it.data() - text.data(), | |
| it.data() - text.data() + it.size()); | |
| } | |
| } | |
| return spans; | |
| } | |
| py::bytes RE2QuoteMetaShim(py::buffer buffer) { | |
| auto bytes = buffer.request(); | |
| auto pattern = FromBytes(bytes); | |
| // Return std::string as bytes. That is, without decoding to str. | |
| return RE2::QuoteMeta(pattern); | |
| } | |
| class Set { | |
| public: | |
| Set(RE2::Anchor anchor, const RE2::Options& options) | |
| : set_(options, anchor) {} | |
| ~Set() = default; | |
| // Not copyable or movable. | |
| Set(const Set&) = delete; | |
| Set& operator=(const Set&) = delete; | |
| int Add(py::buffer buffer) { | |
| auto bytes = buffer.request(); | |
| auto pattern = FromBytes(bytes); | |
| int index = set_.Add(pattern, /*error=*/NULL); // -1 on error | |
| return index; | |
| } | |
| bool Compile() { | |
| // Compiling can fail. | |
| return set_.Compile(); | |
| } | |
| std::vector<int> Match(py::buffer buffer) const { | |
| auto bytes = buffer.request(); | |
| auto text = FromBytes(bytes); | |
| std::vector<int> matches; | |
| py::gil_scoped_release release_gil; | |
| set_.Match(text, &matches); | |
| return matches; | |
| } | |
| private: | |
| RE2::Set set_; | |
| }; | |
| class Filter { | |
| public: | |
| Filter() = default; | |
| ~Filter() = default; | |
| // Not copyable or movable. | |
| Filter(const Filter&) = delete; | |
| Filter& operator=(const Filter&) = delete; | |
| int Add(py::buffer buffer, const RE2::Options& options) { | |
| auto bytes = buffer.request(); | |
| auto pattern = FromBytes(bytes); | |
| int index = -1; // not clobbered on error | |
| filter_.Add(pattern, options, &index); | |
| return index; | |
| } | |
| bool Compile() { | |
| std::vector<std::string> atoms; | |
| filter_.Compile(&atoms); | |
| RE2::Options options; | |
| options.set_literal(true); | |
| options.set_case_sensitive(false); | |
| set_ = std::make_unique<RE2::Set>(options, RE2::UNANCHORED); | |
| for (int i = 0; i < static_cast<int>(atoms.size()); ++i) { | |
| if (set_->Add(atoms[i], /*error=*/NULL) != i) { | |
| // Should never happen: the atom is a literal! | |
| py::pybind11_fail("set_->Add() failed"); | |
| } | |
| } | |
| // Compiling can fail. | |
| return set_->Compile(); | |
| } | |
| std::vector<int> Match(py::buffer buffer, bool potential) const { | |
| if (set_ == nullptr) { | |
| py::pybind11_fail("Match() called before compiling"); | |
| } | |
| auto bytes = buffer.request(); | |
| auto text = FromBytes(bytes); | |
| std::vector<int> atoms; | |
| py::gil_scoped_release release_gil; | |
| set_->Match(text, &atoms); | |
| std::vector<int> matches; | |
| if (potential) { | |
| filter_.AllPotentials(atoms, &matches); | |
| } else { | |
| filter_.AllMatches(text, atoms, &matches); | |
| } | |
| return matches; | |
| } | |
| const RE2& GetRE2(int index) const { | |
| return filter_.GetRE2(index); | |
| } | |
| private: | |
| re2::FilteredRE2 filter_; | |
| std::unique_ptr<RE2::Set> set_; | |
| }; | |
| PYBIND11_MODULE(_re2, module) { | |
| // Translate exceptions thrown by py::pybind11_fail() into Python. | |
| py::register_local_exception<std::runtime_error>(module, "Error"); | |
| module.def("CharLenToBytes", &CharLenToBytes); | |
| module.def("BytesToCharLen", &BytesToCharLen); | |
| // CLASSES | |
| // class RE2 | |
| // enum Anchor | |
| // class Options | |
| // enum Encoding | |
| // class Set | |
| // class Filter | |
| py::class_<RE2> re2(module, "RE2"); | |
| py::enum_<RE2::Anchor> anchor(re2, "Anchor"); | |
| py::class_<RE2::Options> options(re2, "Options"); | |
| py::enum_<RE2::Options::Encoding> encoding(options, "Encoding"); | |
| py::class_<Set> set(module, "Set"); | |
| py::class_<Filter> filter(module, "Filter"); | |
| anchor.value("UNANCHORED", RE2::Anchor::UNANCHORED); | |
| anchor.value("ANCHOR_START", RE2::Anchor::ANCHOR_START); | |
| anchor.value("ANCHOR_BOTH", RE2::Anchor::ANCHOR_BOTH); | |
| encoding.value("UTF8", RE2::Options::Encoding::EncodingUTF8); | |
| encoding.value("LATIN1", RE2::Options::Encoding::EncodingLatin1); | |
| options.def(py::init<>()) | |
| .def_property("max_mem", // | |
| &RE2::Options::max_mem, // | |
| &RE2::Options::set_max_mem) // | |
| .def_property("encoding", // | |
| &RE2::Options::encoding, // | |
| &RE2::Options::set_encoding) // | |
| .def_property("posix_syntax", // | |
| &RE2::Options::posix_syntax, // | |
| &RE2::Options::set_posix_syntax) // | |
| .def_property("longest_match", // | |
| &RE2::Options::longest_match, // | |
| &RE2::Options::set_longest_match) // | |
| .def_property("log_errors", // | |
| &RE2::Options::log_errors, // | |
| &RE2::Options::set_log_errors) // | |
| .def_property("literal", // | |
| &RE2::Options::literal, // | |
| &RE2::Options::set_literal) // | |
| .def_property("never_nl", // | |
| &RE2::Options::never_nl, // | |
| &RE2::Options::set_never_nl) // | |
| .def_property("dot_nl", // | |
| &RE2::Options::dot_nl, // | |
| &RE2::Options::set_dot_nl) // | |
| .def_property("never_capture", // | |
| &RE2::Options::never_capture, // | |
| &RE2::Options::set_never_capture) // | |
| .def_property("case_sensitive", // | |
| &RE2::Options::case_sensitive, // | |
| &RE2::Options::set_case_sensitive) // | |
| .def_property("perl_classes", // | |
| &RE2::Options::perl_classes, // | |
| &RE2::Options::set_perl_classes) // | |
| .def_property("word_boundary", // | |
| &RE2::Options::word_boundary, // | |
| &RE2::Options::set_word_boundary) // | |
| .def_property("one_line", // | |
| &RE2::Options::one_line, // | |
| &RE2::Options::set_one_line); // | |
| re2.def(py::init(&RE2InitShim)) | |
| .def("ok", &RE2::ok) | |
| .def("error", &RE2ErrorShim) | |
| .def("options", &RE2::options) | |
| .def("NumberOfCapturingGroups", &RE2::NumberOfCapturingGroups) | |
| .def("NamedCapturingGroups", &RE2NamedCapturingGroupsShim) | |
| .def("ProgramSize", &RE2::ProgramSize) | |
| .def("ReverseProgramSize", &RE2::ReverseProgramSize) | |
| .def("ProgramFanout", &RE2ProgramFanoutShim) | |
| .def("ReverseProgramFanout", &RE2ReverseProgramFanoutShim) | |
| .def("PossibleMatchRange", &RE2PossibleMatchRangeShim) | |
| .def("Match", &RE2MatchShim) | |
| .def_static("QuoteMeta", &RE2QuoteMetaShim); | |
| set.def(py::init<RE2::Anchor, const RE2::Options&>()) | |
| .def("Add", &Set::Add) | |
| .def("Compile", &Set::Compile) | |
| .def("Match", &Set::Match); | |
| filter.def(py::init<>()) | |
| .def("Add", &Filter::Add) | |
| .def("Compile", &Filter::Compile) | |
| .def("Match", &Filter::Match) | |
| .def("GetRE2", &Filter::GetRE2, | |
| py::return_value_policy::reference_internal); | |
| } | |
| } // namespace re2_python |